You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by ho...@apache.org on 2024/09/13 14:04:11 UTC

(flink-connector-prometheus) branch main updated: [FLINK-33138][Connectors/Prometheus] DataStream sink implementation. AMP request signer. Example application

This is an automated email from the ASF dual-hosted git repository.

hong pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-connector-prometheus.git


The following commit(s) were added to refs/heads/main by this push:
     new a6c8cd5  [FLINK-33138][Connectors/Prometheus] DataStream sink implementation. AMP request signer. Example application
a6c8cd5 is described below

commit a6c8cd57f8c70921512c369873d805ad1d69c4a8
Author: Lorenzo Nicora <ni...@amazon.co.uk>
AuthorDate: Fri Sep 13 09:39:32 2024 +0100

    [FLINK-33138][Connectors/Prometheus] DataStream sink implementation. AMP request signer. Example application
---
 .gitignore                                         |     9 +-
 README.md                                          |    27 +-
 .../README.md                                      |    31 +
 .../pom.xml                                        |    78 +
 .../prometheus/sink/aws/AWS4SignerForAMP.java      |   366 +
 .../AmazonManagedPrometheusWriteRequestSigner.java |   125 +
 .../prometheus/sink/aws/AWS4SignerForAMPTest.java  |    90 +
 ...zonManagedPrometheusWriteRequestSignerTest.java |   129 +
 .../sink/aws/DummAwsSessionCredentialProvider.java |    46 +
 .../sink/aws/DummyAwsBasicCredentialsProvider.java |    42 +
 .../prometheus/sink/aws/RequestHeaderTestUtil.java |    38 +
 flink-connector-prometheus/README.md               |   259 +
 flink-connector-prometheus/pom.xml                 |   117 +
 .../prometheus/sink/HttpResponseCallback.java      |   190 +
 .../PrometheusRemoteWriteHttpRequestBuilder.java   |    79 +
 .../prometheus/sink/PrometheusRequestSigner.java   |    46 +
 .../connector/prometheus/sink/PrometheusSink.java  |   150 +
 .../prometheus/sink/PrometheusSinkBuilder.java     |   243 +
 .../sink/PrometheusSinkConfiguration.java          |   182 +
 .../prometheus/sink/PrometheusSinkException.java   |    36 +
 .../prometheus/sink/PrometheusSinkWriter.java      |   200 +
 .../prometheus/sink/PrometheusStateSerializer.java |   156 +
 .../prometheus/sink/PrometheusTimeSeries.java      |   203 +
 .../sink/PrometheusTimeSeriesConverter.java        |    63 +
 ...usTimeSeriesLabelsAndMetricNameKeySelector.java |    46 +
 .../prometheus/sink/RequestEntrySizeUtils.java     |    65 +
 .../PrometheusSinkWriteException.java              |    51 +
 .../http/PrometheusAsyncHttpClientBuilder.java     |    97 +
 .../sink/http/RemoteWriteResponseClassifier.java   |    41 +
 .../sink/http/RemoteWriteRetryStrategy.java        |   131 +
 .../prometheus/sink/metrics/SinkMetrics.java       |    89 +
 .../sink/metrics/SinkMetricsCallback.java          |    72 +
 .../prometheus/sink/prometheus/Remote.java         |  6661 ++++++++
 .../prometheus/sink/prometheus/Types.java          | 16391 +++++++++++++++++++
 .../prometheus/sink/protobuf/GoGoProtos.java       |   747 +
 .../src/main/proto/gogoproto/gogo.proto            |   144 +
 .../src/main/proto/remote.proto                    |    86 +
 .../src/main/proto/types.proto                     |   105 +
 .../sink/CapturingPrometheusRequestSigner.java     |    59 +
 .../sink/DummyPrometheusRequestSigner.java         |    29 +
 .../prometheus/sink/HttpResponseCallbackTest.java  |   307 +
 .../sink/HttpResponseCallbackTestUtils.java        |    71 +
 .../sink/HttpResponseHandlingBehaviourIT.java      |   343 +
 .../connector/prometheus/sink/HttpTestUtils.java   |    58 +
 .../prometheus/sink/InspectableMetricGroup.java    |   105 +
 .../sink/InspectableMetricGroupAssertions.java     |    65 +
 ...rometheusRemoteWriteHttpRequestBuilderTest.java |    77 +
 .../prometheus/sink/PrometheusSinkWriterTest.java  |   104 +
 .../sink/PrometheusStateSerializerTest.java        |   111 +
 .../sink/PrometheusTimeSeriesConverterTest.java    |   105 +
 ...meSeriesLabelsAndMetricNameKeySelectorTest.java |    99 +
 .../prometheus/sink/PrometheusTimeSeriesTest.java  |   150 +
 .../prometheus/sink/RequestEntrySizeUtilsTest.java |    88 +
 ...iterErrorHandlingBehaviorConfigurationTest.java |    52 +
 .../sink/VerifyableResponseCallback.java           |    78 +
 .../sink/examples/DataStreamExample.java           |   260 +
 .../sink/http/AsyncHttpClientRetryIT.java          |   144 +
 .../prometheus/sink/http/HttpClientTestUtils.java  |    93 +
 .../sink/http/RemoteWriteRetryStrategyTest.java    |   155 +
 .../sink/metrics/SinkMetricsCallbackTest.java      |    90 +
 .../sink/metrics/VerifybleSinkMetricsCallback.java |   105 +
 .../src/test/resources/log4j2.properties           |    30 +
 pom.xml                                            |   194 +
 tools/maven/checkstyle.xml                         |   426 +
 tools/maven/suppressions.xml                       |    31 +
 65 files changed, 31052 insertions(+), 8 deletions(-)

diff --git a/.gitignore b/.gitignore
index 973e8d5..a93c981 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,8 +2,9 @@
 .cache
 scalastyle-output.xml
 .classpath
-.idea/*
+.idea/
 !.idea/vcs.xml
+.run/
 .metadata
 .settings
 .project
@@ -11,7 +12,8 @@ scalastyle-output.xml
 filter.properties
 logs.zip
 .mvn/wrapper/*.jar
-target
+target/
+out/
 tmp
 *.class
 *.iml
@@ -23,7 +25,6 @@ tmp
 .DS_Store
 build-target
 atlassian-ide-plugin.xml
-out/
 /docs/api
 /docs/.bundle
 /docs/.rubydeps
@@ -36,4 +37,4 @@ tools/flink
 tools/flink-*
 tools/releasing/release
 tools/japicmp-output
-*/.idea/
\ No newline at end of file
+dependency-reduced-pom.xml
diff --git a/README.md b/README.md
index 2f26c56..06c4596 100644
--- a/README.md
+++ b/README.md
@@ -2,12 +2,27 @@
 
 This repository contains the official Apache Flink Prometheus connector.
 
+* [More details](flink-connector-prometheus/README.md) about the connector and its usage.
+* [Example application](flink-connector-prometheus/src/test/java/org/apache/flink/connector/prometheus/sink/examples/DataStreamExample.java)
+  demonstrating the usage of the connector.
+
 ## Apache Flink
 
 Apache Flink is an open source stream processing framework with powerful stream- and batch-processing capabilities.
 
 Learn more about Flink at [https://flink.apache.org/](https://flink.apache.org/)
 
+## Modules
+
+This repository contains the following modules
+
+* [Prometheus Connector](./flink-connector-prometheus): Flink Prometheus Connector implementation; supports optional
+  request signer
+* [Sample application](./flink-connector-prometheus/src/test/java/org/apache/flink/connector/prometheus/sink/examples/DataStreamExample.java):
+  This class contains a complete application demonstrating set up and usage of the sink connector.
+* [Amazon Managed Prometheus Request Signer](./flink-connector-prometheus-request-signer-amp): Implementation of request
+  signer for Amazon Managed Prometheus (AMP)
+
 ## Building the Apache Flink Prometheus Connector from Source
 
 Prerequisites:
@@ -31,6 +46,7 @@ The Flink committers use IntelliJ IDEA to develop the Flink codebase.
 We recommend IntelliJ IDEA for developing projects that involve Scala code.
 
 Minimal requirements for an IDE are:
+
 * Support for Java and Scala (also mixed projects)
 * Support for Maven with Java and Scala
 
@@ -41,13 +57,16 @@ The IntelliJ IDE supports Maven out of the box and offers a plugin for Scala dev
 * IntelliJ download: [https://www.jetbrains.com/idea/](https://www.jetbrains.com/idea/)
 * IntelliJ Scala Plugin: [https://plugins.jetbrains.com/plugin/?id=1347](https://plugins.jetbrains.com/plugin/?id=1347)
 
-Check out our [Setting up IntelliJ](https://nightlies.apache.org/flink/flink-docs-master/flinkDev/ide_setup.html#intellij-idea) guide for details.
+Check out
+our [Setting up IntelliJ](https://nightlies.apache.org/flink/flink-docs-master/flinkDev/ide_setup.html#intellij-idea)
+guide for details.
 
 ## Support
 
 Don’t hesitate to ask!
 
-Contact the developers and community on the [mailing lists](https://flink.apache.org/community.html#mailing-lists) if you need any help.
+Contact the developers and community on the [mailing lists](https://flink.apache.org/community.html#mailing-lists) if
+you need any help.
 
 [Open an issue](https://issues.apache.org/jira/browse/FLINK) if you found a bug in Flink.
 
@@ -60,10 +79,10 @@ or in the `docs/` directory of the source code.
 
 This is an active open-source project. We are always open to people who want to use the system or contribute to it.
 Contact us if you are looking for implementation tasks that fit your skills.
-This article describes [how to contribute to Apache Flink](https://flink.apache.org/contributing/how-to-contribute.html).
+This article
+describes [how to contribute to Apache Flink](https://flink.apache.org/contributing/how-to-contribute.html).
 
 ## About
 
 Apache Flink is an open source project of The Apache Software Foundation (ASF).
 The Apache Flink project originated from the [Stratosphere](http://stratosphere.eu) research project.
-
diff --git a/flink-connector-prometheus-request-signer-amp/README.md b/flink-connector-prometheus-request-signer-amp/README.md
new file mode 100644
index 0000000..ede09a1
--- /dev/null
+++ b/flink-connector-prometheus-request-signer-amp/README.md
@@ -0,0 +1,31 @@
+## Request Signer for Amazon Managed Prometheus (AMP)
+
+Request signer implementation for Amazon Managed Prometheus (AMP)
+
+The signer retrieves AWS credential using a `software.amazon.awssdk.auth.credentials.AwsCredentialsProvider`.
+The default `com.amazonaws.auth.DefaultAWSCredentialsProviderChain` works in most of the cases. 
+Alternatively, you can pass an instance of `AwsCredentialsProvider` to the signer constructor.
+
+The Flink application requires `RemoteWrite` permissions to the AMP workspace (e.g. `AmazonPromethusRemoteWriteAccess`
+policy).
+
+### Sample usage
+
+To enable request signing for Amazon Managed Prometheus, and instance of `AmazonManagedPrometheusWriteRequestSigner`
+must be provided when building the `PrometheusSink` instance. The only required parameters are the AWS region and the 
+AMP remote-write URL.
+
+```java
+
+// AWS region of the AMP workspace
+String prometheusRegion = "us-east-1";
+
+// Remote-Write URL of the AMP workspace
+String prometheusRemoteWriteUrl = "https://aps-workspaces.us-east-1.amazonaws.com/workspaces/ws-091245678-9abc-def0-1234-56789abcdef0/api/v1/remote_write";
+
+// Build the sink to AMP using the request signer
+AsyncSinkBase<PrometheusTimeSeries, Types.TimeSeries> sink = PrometheusSink.builder()
+        .setPrometheusRemoteWriteUrl(prometheusRemoteWriteUrl)
+        .setRequestSigner(new AmazonManagedPrometheusWriteRequestSigner(prometheusRemoteWriteUrl, prometheusRegion))
+        .build();
+```
diff --git a/flink-connector-prometheus-request-signer-amp/pom.xml b/flink-connector-prometheus-request-signer-amp/pom.xml
new file mode 100644
index 0000000..d7292b8
--- /dev/null
+++ b/flink-connector-prometheus-request-signer-amp/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.flink</groupId>
+        <artifactId>flink-connector-prometheus-parent</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+
+    <name>Flink : Connectors : Prometheus : Amazon Managed Prometheus Request Signer</name>
+    <artifactId>flink-connector-prometheus-request-signer-amp</artifactId>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>software.amazon.awssdk</groupId>
+                <artifactId>bom</artifactId>
+                <version>${aws.sdkv2.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.flink</groupId>
+            <artifactId>flink-connector-prometheus</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>auth</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.flink</groupId>
+            <artifactId>flink-core</artifactId>
+            <version>${flink.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- test -->
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+
+</project>
\ No newline at end of file
diff --git a/flink-connector-prometheus-request-signer-amp/src/main/java/org/apache/flink/connector/prometheus/sink/aws/AWS4SignerForAMP.java b/flink-connector-prometheus-request-signer-amp/src/main/java/org/apache/flink/connector/prometheus/sink/aws/AWS4SignerForAMP.java
new file mode 100644
index 0000000..15ac4e3
--- /dev/null
+++ b/flink-connector-prometheus-request-signer-amp/src/main/java/org/apache/flink/connector/prometheus/sink/aws/AWS4SignerForAMP.java
@@ -0,0 +1,366 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.aws;
+
+import org.apache.flink.annotation.Internal;
+import org.apache.flink.util.StringUtils;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.SimpleTimeZone;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * Implements Http/1.1 AWS Signature Version 4a request signing for POST requests to the Amazon
+ * Managed Prometheus service (`aps`).
+ */
+@Internal
+public class AWS4SignerForAMP {
+    private static final String SCHEME = "AWS4";
+    private static final String ALGORITHM = "HMAC-SHA256";
+    private static final String TERMINATOR = "aws4_request";
+    private static final String HTTP_METHOD = "POST";
+    private static final String SERVICE = "aps";
+
+    private static final String SHA_256 = "SHA-256";
+    private static final String HMAC_SHA_256 = "HmacSHA256";
+
+    // Format strings for the date/time and date stamps required during signing
+    private static final String ISO_8601_BASIC_FORMAT = "yyyyMMdd'T'HHmmss'Z'";
+    private static final String DATE_STRING_FORMAT = "yyyyMMdd";
+
+    // Header names
+    private static final String X_AMZ_DATE = "x-amz-date";
+    private static final String X_AMZ_SECURITY_TOKEN = "x-amz-security-token";
+    private static final String HOST = "Host";
+
+    private final URL endpointUrl;
+    private final String regionName;
+
+    private final SimpleDateFormat dateTimeFormat;
+    private final SimpleDateFormat dateStampFormat;
+
+    /**
+     * Create a new Http/1.1 AWS V4a request signer.
+     *
+     * @param endpointUrl The service endpoint, including the path to any resource.
+     * @param regionName The system name of the AWS region associated with the endpoint, e.g.
+     *     us-east-1.
+     */
+    public AWS4SignerForAMP(URL endpointUrl, String regionName) {
+        this.endpointUrl = endpointUrl;
+        this.regionName = regionName;
+
+        dateTimeFormat = new SimpleDateFormat(ISO_8601_BASIC_FORMAT);
+        dateTimeFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
+        dateStampFormat = new SimpleDateFormat(DATE_STRING_FORMAT);
+        dateStampFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
+    }
+
+    private static String urlEncode(String url, boolean keepPathSlash) {
+
+        String encoded;
+        try {
+            encoded = URLEncoder.encode(url, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("UTF-8 encoding is not supported.", e);
+        }
+        if (keepPathSlash) {
+            encoded = encoded.replace("%2F", "/");
+        }
+        return encoded;
+    }
+
+    /**
+     * Returns the canonical collection of header names that will be included in the signature. For
+     * AWS4, all header names must be included in the process in sorted canonicalized order.
+     */
+    private static String getCanonicalizeHeaderNames(Map<String, String> headers) {
+        List<String> sortedHeaders = new ArrayList<String>(headers.keySet());
+        sortedHeaders.sort(String.CASE_INSENSITIVE_ORDER);
+
+        StringBuilder buffer = new StringBuilder();
+        for (String header : sortedHeaders) {
+            if (buffer.length() > 0) {
+                buffer.append(";");
+            }
+            buffer.append(header.toLowerCase());
+        }
+
+        return buffer.toString();
+    }
+
+    /**
+     * Computes the canonical headers with values for the request. For AWS4, all headers must be
+     * included in the signing process.
+     */
+    private static String getCanonicalizedHeaderString(Map<String, String> headers) {
+        if (headers == null || headers.isEmpty()) {
+            return "";
+        }
+
+        // step1: sort the headers by case-insensitive order
+        List<String> sortedHeaders = new ArrayList<>(headers.keySet());
+        sortedHeaders.sort(String.CASE_INSENSITIVE_ORDER);
+
+        // step2: form the canonical header:value entries in sorted order.
+        // Multiple white spaces in the values should be compressed to a single
+        // space.
+        StringBuilder buffer = new StringBuilder();
+        for (String key : sortedHeaders) {
+            buffer.append(key.toLowerCase().replaceAll("\\s+", " "))
+                    .append(":")
+                    .append(headers.get(key).replaceAll("\\s+", " "));
+            buffer.append("\n");
+        }
+
+        return buffer.toString();
+    }
+
+    /** Returns the canonicalized resource path for the service endpoint. */
+    private static String getCanonicalizedResourcePath(URL endpoint) {
+        if (endpoint == null) {
+            return "/";
+        }
+        String path = endpoint.getPath();
+        if (path == null || path.isEmpty()) {
+            return "/";
+        }
+
+        String encodedPath = urlEncode(path, true);
+        if (encodedPath.startsWith("/")) {
+            return encodedPath;
+        } else {
+            return "/".concat(encodedPath);
+        }
+    }
+
+    /**
+     * Returns the canonical request string to go into the signer process; this consists of several
+     * canonical sub-parts.
+     */
+    private static String getCanonicalRequest(
+            URL endpoint,
+            String queryParameters,
+            String canonicalizedHeaderNames,
+            String canonicalizedHeaders,
+            String bodyHash) {
+
+        return HTTP_METHOD
+                + '\n'
+                + getCanonicalizedResourcePath(endpoint)
+                + '\n'
+                + queryParameters
+                + '\n'
+                + canonicalizedHeaders
+                + '\n'
+                + canonicalizedHeaderNames
+                + '\n'
+                + bodyHash;
+    }
+
+    /**
+     * Examines the specified query string parameters and returns a canonicalized form.
+     *
+     * <p>The canonicalized query string is formed by first sorting all the query string parameters,
+     * then URI encoding both the key and value and then joining them, in order, separating key
+     * value pairs with an '&'.
+     *
+     * @param parameters The query string parameters to be canonicalized.
+     * @return A canonicalized form for the specified query string parameters.
+     */
+    private static String getCanonicalizedQueryString(Map<String, String> parameters) {
+        if (parameters == null || parameters.isEmpty()) {
+            return "";
+        }
+
+        SortedMap<String, String> sorted = new TreeMap<>();
+
+        Iterator<Map.Entry<String, String>> pairs = parameters.entrySet().iterator();
+        while (pairs.hasNext()) {
+            Map.Entry<String, String> pair = pairs.next();
+            String key = pair.getKey();
+            String value = pair.getValue();
+            sorted.put(urlEncode(key, false), urlEncode(value, false));
+        }
+
+        StringBuilder builder = new StringBuilder();
+        pairs = sorted.entrySet().iterator();
+        while (pairs.hasNext()) {
+            Map.Entry<String, String> pair = pairs.next();
+            builder.append(pair.getKey());
+            builder.append("=");
+            builder.append(pair.getValue());
+            if (pairs.hasNext()) {
+                builder.append("&");
+            }
+        }
+
+        return builder.toString();
+    }
+
+    /** Generates the StringToSing. */
+    private static String stringToSign(String dateTime, String scope, String canonicalRequest) {
+        return SCHEME
+                + "-"
+                + ALGORITHM
+                + "\n"
+                + dateTime
+                + "\n"
+                + scope
+                + "\n"
+                + toHex(hash(canonicalRequest));
+    }
+
+    private static byte[] hmacSHA256Sign(String stringData, byte[] key) {
+        try {
+            byte[] data = stringData.getBytes(StandardCharsets.UTF_8);
+            Mac mac = Mac.getInstance(HMAC_SHA_256);
+            mac.init(new SecretKeySpec(key, HMAC_SHA_256));
+            return mac.doFinal(data);
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Unable to calculate a request signature: " + e.getMessage(), e);
+        }
+    }
+
+    /** Hashes the string contents (assumed to be UTF-8) using the SHA-256 algorithm. */
+    public static byte[] hash(String text) {
+        try {
+            MessageDigest md = MessageDigest.getInstance(SHA_256);
+            md.update(text.getBytes(StandardCharsets.UTF_8));
+            return md.digest();
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Unable to compute hash while signing request: " + e.getMessage(), e);
+        }
+    }
+
+    /** Hashes the byte array using the SHA-256 algorithm. */
+    public static byte[] hash(byte[] data) {
+        try {
+            MessageDigest md = MessageDigest.getInstance(SHA_256);
+            md.update(data);
+            return md.digest();
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Unable to compute hash while signing request: " + e.getMessage(), e);
+        }
+    }
+
+    /** Converts a byte[] to a lowercase hex string. */
+    public static String toHex(byte[] data) {
+        return StringUtils.byteToHexString(data);
+    }
+
+    /**
+     * Computes an AWS4 signature for a request, ready for inclusion as an 'Authorization' header.
+     *
+     * @param headers The request headers; 'Host' and 'X-Amz-Date' will be added to this set.
+     * @param queryParameters Any query parameters that will be added to the endpoint. The
+     *     parameters should be specified in canonical format.
+     * @param bodyHash Precomputed SHA256 hash of the request body content; this value should also
+     *     be set as the header 'X-Amz-Content-SHA256' for non-streaming uploads.
+     * @param awsAccessKey The user's AWS Access Key.
+     * @param awsSecretKey The user's AWS Secret Key.
+     * @return The computed authorization string for the request. This value needs to be set as the
+     *     header 'Authorization' on the subsequent HTTP request.
+     */
+    public String computeSignature(
+            Map<String, String> headers,
+            Map<String, String> queryParameters,
+            String bodyHash,
+            String awsAccessKey,
+            String awsSecretKey,
+            String sessionToken) {
+        // first get the date and time for the subsequent request, and convert
+        // to ISO 8601 format for use in signature generation
+        Date now = new Date();
+        String dateTimeStamp = dateTimeFormat.format(now);
+
+        // update the headers with required 'x-amz-date' and 'host' values
+        headers.put(X_AMZ_DATE, dateTimeStamp);
+        if (sessionToken != null && !sessionToken.isEmpty()) {
+            headers.put(X_AMZ_SECURITY_TOKEN, sessionToken);
+        }
+
+        String hostHeader = endpointUrl.getHost();
+        int port = endpointUrl.getPort();
+        if (port > -1) {
+            hostHeader = hostHeader.concat(":" + port);
+        }
+        headers.put(HOST, hostHeader);
+
+        // canonicalize the headers; we need the set of header names as well as the
+        // names and values to go into the signature process
+        String canonicalizedHeaderNames = getCanonicalizeHeaderNames(headers);
+        String canonicalizedHeaders = getCanonicalizedHeaderString(headers);
+
+        // if any query string parameters have been supplied, canonicalize them
+        String canonicalizedQueryParameters = getCanonicalizedQueryString(queryParameters);
+
+        // canonicalize the various components of the request
+        String canonicalRequest =
+                getCanonicalRequest(
+                        endpointUrl,
+                        canonicalizedQueryParameters,
+                        canonicalizedHeaderNames,
+                        canonicalizedHeaders,
+                        bodyHash);
+
+        // construct the string to be signed
+        String dateStamp = dateStampFormat.format(now);
+        String scope = dateStamp + "/" + regionName + "/" + SERVICE + "/" + TERMINATOR;
+        String stringToSign = stringToSign(dateTimeStamp, scope, canonicalRequest);
+
+        // compute the signing key
+        byte[] kSecret = (SCHEME + awsSecretKey).getBytes();
+        byte[] kDate = hmacSHA256Sign(dateStamp, kSecret);
+        byte[] kRegion = hmacSHA256Sign(regionName, kDate);
+        byte[] kService = hmacSHA256Sign(SERVICE, kRegion);
+        byte[] kSigning = hmacSHA256Sign(TERMINATOR, kService);
+        byte[] signature = hmacSHA256Sign(stringToSign, kSigning);
+
+        String credentialsAuthorizationHeader = "Credential=" + awsAccessKey + "/" + scope;
+        String signedHeadersAuthorizationHeader = "SignedHeaders=" + canonicalizedHeaderNames;
+        String signatureAuthorizationHeader = "Signature=" + toHex(signature);
+
+        return SCHEME
+                + "-"
+                + ALGORITHM
+                + " "
+                + credentialsAuthorizationHeader
+                + ", "
+                + signedHeadersAuthorizationHeader
+                + ", "
+                + signatureAuthorizationHeader;
+    }
+}
diff --git a/flink-connector-prometheus-request-signer-amp/src/main/java/org/apache/flink/connector/prometheus/sink/aws/AmazonManagedPrometheusWriteRequestSigner.java b/flink-connector-prometheus-request-signer-amp/src/main/java/org/apache/flink/connector/prometheus/sink/aws/AmazonManagedPrometheusWriteRequestSigner.java
new file mode 100644
index 0000000..55ba7ae
--- /dev/null
+++ b/flink-connector-prometheus-request-signer-amp/src/main/java/org/apache/flink/connector/prometheus/sink/aws/AmazonManagedPrometheusWriteRequestSigner.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.aws;
+
+import org.apache.flink.annotation.PublicEvolving;
+import org.apache.flink.connector.prometheus.sink.PrometheusRequestSigner;
+import org.apache.flink.util.Preconditions;
+
+import org.apache.commons.lang3.StringUtils;
+import software.amazon.awssdk.auth.credentials.AwsCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
+import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Map;
+
+/**
+ * Sign a Remote-Write request to Amazon Managed Service for Prometheus (AMP).
+ *
+ * <p>On every request, AWS credentials are retrieved using an AwsCredentialsProvider, and used to
+ * sign the request, with AWS Signature Version 4a.
+ */
+@PublicEvolving
+public class AmazonManagedPrometheusWriteRequestSigner implements PrometheusRequestSigner {
+    // Header names
+    private static final String X_AMZ_CONTENT_SHA_256 = "x-amz-content-sha256";
+    private static final String AUTHORIZATION = "Authorization";
+
+    private final URL remoteWriteUrl;
+    private final String awsRegion;
+
+    private final AwsCredentialsProvider awsCredProvider;
+
+    /**
+     * Creates a signer instance with a specified credential provider.
+     *
+     * @param remoteWriteUrl URL of the remote-write endpoint
+     * @param awsRegion Region of the AMP workspace
+     * @param awsCredProvider implementation of AwsCredentialsProvider to retrieve the credentials
+     */
+    public AmazonManagedPrometheusWriteRequestSigner(
+            String remoteWriteUrl, String awsRegion, AwsCredentialsProvider awsCredProvider) {
+        Preconditions.checkArgument(
+                StringUtils.isNotBlank(awsRegion), "awsRegion cannot be null or empty");
+        Preconditions.checkArgument(
+                StringUtils.isNotBlank(remoteWriteUrl), "remoteWriteUrl cannot be null or empty");
+        Preconditions.checkArgument(awsCredProvider != null, "credentialsProvider cannot be null");
+
+        this.awsRegion = awsRegion;
+        try {
+            this.remoteWriteUrl = new URL(remoteWriteUrl);
+        } catch (MalformedURLException e) {
+            throw new IllegalArgumentException(
+                    "Invalid AMP remote-write URL: " + remoteWriteUrl, e);
+        }
+        this.awsCredProvider = awsCredProvider;
+    }
+
+    /**
+     * Creates a signer instance using the default AWS credentials provider chain.
+     *
+     * @param remoteWriteUrl URL of the remote-write endpoint
+     * @param awsRegion Region of the AMP workspace
+     */
+    public AmazonManagedPrometheusWriteRequestSigner(String remoteWriteUrl, String awsRegion) {
+        this(remoteWriteUrl, awsRegion, DefaultCredentialsProvider.create());
+    }
+
+    /**
+     * Add the additional Http request headers required by Amazon Managed Prometheus:
+     * 'x-amz-content-sha256', 'Host', 'X-Amz-Date', 'x-amz-security-token' and 'Authorization`.
+     *
+     * @param requestHeaders original Http request headers. It must be mutable. For efficiency, any
+     *     new header is added to the map, instead of making a copy.
+     * @param requestBody request body, already compressed
+     */
+    @Override
+    public void addSignatureHeaders(Map<String, String> requestHeaders, byte[] requestBody) {
+        byte[] contentHash = AWS4SignerForAMP.hash(requestBody);
+        String contentHashString = AWS4SignerForAMP.toHex(contentHash);
+
+        // x-amz-content-sha256 must be included before generating the Authorization header
+        requestHeaders.put(X_AMZ_CONTENT_SHA_256, contentHashString);
+
+        // Get the credentials from the default credential provider chain
+        AwsCredentials awsCreds = awsCredProvider.resolveCredentials();
+
+        // If the credentials are from a session, also get the session token
+        String sessionToken =
+                (awsCreds instanceof AwsSessionCredentials)
+                        ? ((AwsSessionCredentials) awsCreds).sessionToken()
+                        : null;
+
+        AWS4SignerForAMP signer = new AWS4SignerForAMP(remoteWriteUrl, awsRegion);
+
+        // computeSignature also adds 'Host', 'X-Amz-Date' and 'x-amz-security-token' to the
+        // requestHeaders Map
+        String authorization =
+                signer.computeSignature(
+                        requestHeaders,
+                        null, // no query parameters
+                        contentHashString,
+                        awsCreds.accessKeyId(),
+                        awsCreds.secretAccessKey(),
+                        sessionToken);
+        requestHeaders.put(AUTHORIZATION, authorization);
+    }
+}
diff --git a/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/AWS4SignerForAMPTest.java b/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/AWS4SignerForAMPTest.java
new file mode 100644
index 0000000..ec74b1a
--- /dev/null
+++ b/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/AWS4SignerForAMPTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.aws;
+
+import org.junit.jupiter.api.Test;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.flink.connector.prometheus.sink.aws.RequestHeaderTestUtil.assertContainsHeader;
+import static org.apache.flink.connector.prometheus.sink.aws.RequestHeaderTestUtil.assertDoesNotContainHeader;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AWS4SignerForAMPTest {
+
+    private final AWS4SignerForAMP signer =
+            new AWS4SignerForAMP(new URL("http://example.com/endpoint"), "us-east-1");
+
+    public AWS4SignerForAMPTest() throws MalformedURLException {}
+
+    @Test
+    public void shouldAddExpectedHeaders_noSessionToken() throws Exception {
+
+        Map<String, String> headers = new HashMap<>();
+
+        signer.computeSignature(headers, null, "body-hash", "access-key", "secret-key", null);
+
+        assertContainsHeader("x-amz-date", headers);
+        assertContainsHeader("Host", headers);
+        assertDoesNotContainHeader("x-amz-security-token", headers);
+    }
+
+    @Test
+    public void shouldAddExpectedHeaders_sessionToken() throws Exception {
+        Map<String, String> headers = new HashMap<>();
+
+        signer.computeSignature(
+                headers, null, "body-hash", "access-key", "secret-key", "session-token");
+
+        assertContainsHeader("x-amz-date", headers);
+        assertContainsHeader("Host", headers);
+        assertContainsHeader("x-amz-security-token", headers);
+    }
+
+    @Test
+    public void signatureShouldMatchPattern_SessionToken() throws Exception {
+        String signature =
+                signer.computeSignature(
+                        new HashMap<>(),
+                        null,
+                        "body-hash",
+                        "ACC355K3Y",
+                        "secret-key",
+                        "session-token");
+
+        String expectedSignaturePatternWirthSecurityToken =
+                "AWS4-HMAC-SHA256 Credential=([A-Za-z0-9]+)/([0-9]{8})/([a-z0-9-]+)/aps/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=([a-f0-9]{64})";
+
+        assertTrue(signature.matches(expectedSignaturePatternWirthSecurityToken));
+    }
+
+    @Test
+    public void signatureShouldMatchPattern_noSessionToken() {
+        String signature =
+                signer.computeSignature(
+                        new HashMap<>(), null, "body-hash", "ACC355K3Y", "secret-key", null);
+
+        String expectedSignaturePatternWithoutSecurityToken =
+                "AWS4-HMAC-SHA256 Credential=([A-Za-z0-9]+)/([0-9]{8})/([a-z0-9-]+)/aps/aws4_request, SignedHeaders=host;x-amz-date, Signature=([a-f0-9]{64})";
+
+        assertTrue(signature.matches(expectedSignaturePatternWithoutSecurityToken));
+    }
+}
diff --git a/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/AmazonManagedPrometheusWriteRequestSignerTest.java b/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/AmazonManagedPrometheusWriteRequestSignerTest.java
new file mode 100644
index 0000000..67ccccf
--- /dev/null
+++ b/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/AmazonManagedPrometheusWriteRequestSignerTest.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.aws;
+
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.flink.connector.prometheus.sink.aws.RequestHeaderTestUtil.assertContainsHeader;
+import static org.apache.flink.connector.prometheus.sink.aws.RequestHeaderTestUtil.assertDoesNotContainHeader;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class AmazonManagedPrometheusWriteRequestSignerTest {
+
+    @Test
+    public void constructorShouldFailIfRemoteWriteUrlIsBlankString() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> {
+                    new AmazonManagedPrometheusWriteRequestSigner(" ", "us-east-1");
+                });
+    }
+
+    @Test
+    public void constructorShouldFailIfRemoteWriteUrlIsNull() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> {
+                    new AmazonManagedPrometheusWriteRequestSigner(null, "us-east-1");
+                });
+    }
+
+    @Test
+    public void constructorShouldSucceedIfRemoteWriteUrlIsValidURL() {
+        assertDoesNotThrow(
+                () -> {
+                    new AmazonManagedPrometheusWriteRequestSigner(
+                            "https://example.com", "us-east-1");
+                });
+    }
+
+    @Test
+    public void constructorShouldFaiIfRemoteWriteUrlIsInvalidURL() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> {
+                    new AmazonManagedPrometheusWriteRequestSigner("invalid-url", "us-east-1");
+                });
+    }
+
+    @Test
+    public void constructorShouldFaiIfRegionIsBlankString() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> {
+                    new AmazonManagedPrometheusWriteRequestSigner("https://example.com", " ");
+                });
+    }
+
+    @Test
+    public void constructorShouldFaiIfRegionIsNull() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> {
+                    new AmazonManagedPrometheusWriteRequestSigner("https://example.com", null);
+                });
+    }
+
+    @Test
+    public void shouldAddExpectedHeaders_BasicCredentials() {
+        AmazonManagedPrometheusWriteRequestSigner signer =
+                new AmazonManagedPrometheusWriteRequestSigner(
+                        "https://example.com/endpoint",
+                        "us-east-1",
+                        new DummyAwsBasicCredentialsProvider("access-key-id", "secret-access-key"));
+
+        Map<String, String> requestHeaders = new HashMap<>();
+        byte[] requestBody = "request-payload".getBytes(StandardCharsets.UTF_8);
+
+        signer.addSignatureHeaders(requestHeaders, requestBody);
+
+        assertContainsHeader("Authorization", requestHeaders);
+        assertContainsHeader("x-amz-content-sha256", requestHeaders);
+        assertContainsHeader("x-amz-date", requestHeaders);
+        assertContainsHeader("Host", requestHeaders);
+        // The security token is only expected with session credentials
+        assertDoesNotContainHeader("x-amz-security-token", requestHeaders);
+    }
+
+    @Test
+    public void shouldAddExpectedHeaders_SessionCredentials() {
+        AmazonManagedPrometheusWriteRequestSigner signer =
+                new AmazonManagedPrometheusWriteRequestSigner(
+                        "https://example.com/endpoint",
+                        "us-east-1",
+                        new DummAwsSessionCredentialProvider(
+                                "access-key-id", "secret-access-key", "session-key"));
+
+        Map<String, String> requestHeaders = new HashMap<>();
+        byte[] requestBody = "request-payload".getBytes(StandardCharsets.UTF_8);
+
+        signer.addSignatureHeaders(requestHeaders, requestBody);
+
+        assertContainsHeader("Authorization", requestHeaders);
+        assertContainsHeader("x-amz-content-sha256", requestHeaders);
+        assertContainsHeader("x-amz-date", requestHeaders);
+        assertContainsHeader("Host", requestHeaders);
+        // With Session credentials should have the securitu token
+        assertContainsHeader("x-amz-security-token", requestHeaders);
+    }
+}
diff --git a/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/DummAwsSessionCredentialProvider.java b/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/DummAwsSessionCredentialProvider.java
new file mode 100644
index 0000000..dc67945
--- /dev/null
+++ b/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/DummAwsSessionCredentialProvider.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.aws;
+
+import software.amazon.awssdk.auth.credentials.AwsCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
+
+/** Dummy AwsCredentialsProvider returning AwsSessionCredentials. */
+public class DummAwsSessionCredentialProvider implements AwsCredentialsProvider {
+
+    private final String accessKeyId;
+    private final String secretAccessKey;
+    private final String sessionToken;
+
+    public DummAwsSessionCredentialProvider(
+            String accessKeyId, String secretAccessKey, String sessionToken) {
+        this.accessKeyId = accessKeyId;
+        this.secretAccessKey = secretAccessKey;
+        this.sessionToken = sessionToken;
+    }
+
+    @Override
+    public AwsCredentials resolveCredentials() {
+        return AwsSessionCredentials.builder()
+                .accessKeyId(accessKeyId)
+                .secretAccessKey(secretAccessKey)
+                .sessionToken(sessionToken)
+                .build();
+    }
+}
diff --git a/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/DummyAwsBasicCredentialsProvider.java b/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/DummyAwsBasicCredentialsProvider.java
new file mode 100644
index 0000000..9884bc6
--- /dev/null
+++ b/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/DummyAwsBasicCredentialsProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.aws;
+
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+
+/** Dummy AwsCredentialsProvider returning AwsBasicCredentials. */
+public class DummyAwsBasicCredentialsProvider implements AwsCredentialsProvider {
+
+    private final String accessKeyId;
+    private final String secretAccessKey;
+
+    public DummyAwsBasicCredentialsProvider(String accessKeyId, String secretAccessKey) {
+        this.accessKeyId = accessKeyId;
+        this.secretAccessKey = secretAccessKey;
+    }
+
+    @Override
+    public AwsCredentials resolveCredentials() {
+        return AwsBasicCredentials.builder()
+                .accessKeyId(accessKeyId)
+                .secretAccessKey(secretAccessKey)
+                .build();
+    }
+}
diff --git a/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/RequestHeaderTestUtil.java b/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/RequestHeaderTestUtil.java
new file mode 100644
index 0000000..64857a0
--- /dev/null
+++ b/flink-connector-prometheus-request-signer-amp/src/test/java/org/apache/flink/connector/prometheus/sink/aws/RequestHeaderTestUtil.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.aws;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class RequestHeaderTestUtil {
+    static void assertContainsHeader(String expectedHeaderName, Map<String, String> requestHeader) {
+        assertTrue(
+                requestHeader.containsKey(expectedHeaderName),
+                "Missing header: " + expectedHeaderName);
+    }
+
+    static void assertDoesNotContainHeader(
+            String expectedHeaderName, Map<String, String> requestHeader) {
+        assertFalse(
+                requestHeader.containsKey(expectedHeaderName),
+                "Unexpected header: " + expectedHeaderName);
+    }
+}
diff --git a/flink-connector-prometheus/README.md b/flink-connector-prometheus/README.md
new file mode 100644
index 0000000..0343a1c
--- /dev/null
+++ b/flink-connector-prometheus/README.md
@@ -0,0 +1,259 @@
+## Flink Prometheus connector (sink)
+
+Implementation of the Prometheus sink connector for DataStream API.
+
+The sink writes to Prometheus using the Remote-Write interface, based
+on [Remote-Write specifications version 1.0](https://prometheus.io/docs/concepts/remote_write_spec/)
+
+### Guarantees and input restrictions
+
+Due to the strict [ordering](https://prometheus.io/docs/concepts/remote_write_spec/#ordering)
+and [format](https://prometheus.io/docs/concepts/remote_write_spec/#labels) requirements of Prometheus Remote-Write, the sink requires that users ensure that input data is in 
+order and well-formed, before sending it to the sink.
+
+For efficiency, the connector does not do any validation.
+If input is out of order or malformed, **the write request is rejected by Prometheus**.
+If the write request is rejected, depending on the configured [error handling behaviour](#error-handling-behavior) for
+"Prometheus non-retriable errors", the sink will either throw an exception (`FAIL`, default behavior) or discard the
+offending
+request and continue (`DISCARD_AND_CONTINUE`). See [error handling behaviour](#error-handling-behavior), below, for
+further details.
+
+The sink receives as input time-series, each containing one or more samples.
+To optimise the write throughput, input time-series are batched, in the order they are received, and written with a
+single write-request.
+
+If a write-request contains any out-of-order or malformed data, **the entire request is rejected** and all time series
+are discarded.
+The reason is Remote-Write
+specifications [explicitly forbids retrying](https://prometheus.io/docs/concepts/remote_write_spec/#retries-backoff) of
+rejected write requests (4xx responses).
+and the Prometheus response does not contain enough information to efficiently partially retry the write, discarding the
+offending data.
+
+### Responsibilities of the application
+
+It is responsibility of the application sending the data to the sink in the correct order and format.
+
+1. Input time-series must be well-formed, e.g. only valid and non-duplicated labels,
+   samples in timestamp order (see [Labels and Ordering](https://prometheus.io/docs/concepts/remote_write_spec/#labels)
+   in Prometheus Remote-Write specs).
+2. Input time-series with identical labels are sent to the sink in timestamp order.
+3. If sink parallelism > 1 is used, the input stream must be partitioned so that all time-series with identical labels
+   go to the same sink subtask. A `KeySelector` is provided to partition input correctly (
+   see [Partitioning](#partitioning), below).
+
+#### Sink input objects
+
+To help sending well-formed data to the sink, the connector
+expect [`PrometheusTimeSeries`](./src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusTimeSeries.java)
+POJOs as input.
+
+Each `PrometheusTimeSeries` instance maps 1-to-1 to
+a [remote-write `TimeSeries`](https://prometheus.io/docs/concepts/remote_write_spec/#protocol). Each object contains:
+
+* exactly one `metericName`, mapped to the special  `__name__` label
+* optionally, any number of additional labels { k: String, v:String } - MUST BE IN ORDER BY KEY
+* one or more `Samples` { value: double, timestamp: long } - MUST BE IN TIMESTAMP ORDER
+
+`PrometheusTimeSeries` provides a builder interface.
+
+```java
+
+// List<Tuple2<Double, Long>> samples = ...
+
+PrometheusTimeSeries.Builder tsBuilder = PrometheusTimeSeries.builder()
+        .withMetricName("CPU") // mapped to  `__name__` label
+        .addLabel("InstanceID", instanceId)
+        .addLabel("AccountID", accountId);
+    
+for(
+Tuple2<Double, Long> sample :samples){
+        tsBuilder.
+
+addSample(sample.f0, sample.f1);
+}
+
+PrometheusTimeSeries ts = tsBuilder.build();
+```
+
+#### Input data validation
+
+Prometheus imposes strict constraints to the content sent to remote-write, including label format and ordering, sample
+time ordering etc.
+
+For efficiency, the sink **does not do any validation or reordering** of the input. It's responsibility
+of the application ensuring that input is well-formed.
+
+Any malformed data will be rejected on write to Prometheus. Depending on
+the [error handling behaviours](#error-handling-behavior)
+configured, the sink will throw an exception stopping the job (default), or drop the entire write-request, log the fact,
+and continue.
+
+For complete details about these constraints, refer to
+the [remote-write specifications](https://prometheus.io/docs/concepts/remote_write_spec/).
+
+### Batching, blocking writes and retry
+
+The sink batches multiple time-series into a single write-request, retaining the order..
+
+Batching is based on the number of samples. Each write-request contains up to 500 samples, with a max buffering time of
+5 seconds
+(both configurable). The number of time-series doesn't matter.
+
+As by [Prometheus Remote-Write specifications](https://prometheus.io/docs/concepts/remote_write_spec/#retries-backoff),
+the sink retries 5xx and 429 responses. Retrying is blocking, to retain sample ordering, and uses and exponential
+backoff.
+
+The exponential backoff starts with an initial delay (default 30 ms) and increases it exponentially up to a max retry
+delay (default 5 sec). It continues retrying until the max number of retries is reached (default reties forever).
+
+On non-retriable error response (4xx, except 429, non retryable exceptions), or on reaching the retry limit, depending
+on
+the configured [error handling behaviour](#error-handling-behavior) for "Max retries exceeded", the sink will either
+throw
+an exception (`FAIL`, default behaviour), or **discard the entire write-request**, log a warning and continue. See
+[error handling behaviour](#error-handling-behavior), below, for further details.
+
+### Initializing the sink
+
+Example of sink initialisation (for documentation purposes, we are setting all parameters to their default values):
+Sink
+
+```java
+PrometheusSink sink = PrometheusSink.builder()
+        .setMaxBatchSizeInSamples(500)              // Batch size (write-request size), in samples (default: 500)
+        .setMaxRecordSizeInSamples(500)             // Max sink input record size, in samples (default: 500), must be <= maxBatchSizeInSamples - If exceeded the job will continuously fail and restart!
+        .setMaxTimeInBufferMS(5000)                 // Max time a time-series is buffered for batching (default: 5000 ms)
+        .setRetryConfiguration(RetryConfiguration.builder()
+                .setInitialRetryDelayMS(30L)            // Initial retry delay (default: 30 ms)
+                .setMaxRetryDelayMS(5000L)              // Maximum retry delay, with exponential backoff (default: 5000 ms)
+                .setMaxRetryCount(100)                  // Max number of retries (default: 100)
+                .build())
+        .setSocketTimeoutMs(5000)                   // Http client socket timeout (default: 5000 ms)
+        .setPrometheusRemoteWriteUrl(prometheusRemoteWriteUrl)  // Remote-write URL
+        .setRequestSigner(new AmazonManagedPrometheusWriteRequestSigner(prometheusRemoteWriteUrl, prometheusRegion)) // Optional request signed (AMP request signer in this example)
+        .setErrorHandlingBehaviourConfiguration(SinkWriterErrorHandlingBehaviorConfiguration.builder()
+                // Error handling behaviors. See description below, for more details. All behaviors default to FAIL   
+                .onPrometheusNonRetriableError(OnErrorBehavior.FAIL)
+                .onMaxRetryExceeded(OnErrorBehavior.FAIL)
+                .onHttpClientIOFail(OnErrorBehavior.FAIL)
+                .build())
+        .setMetricGroupName("Prometheus")           // Customizable metric-group suffix (default: "Prometheus")
+        .build();
+```
+
+### Partitioning
+
+When the sink has parallelism > 1, the stream must be partitioned so that all time-series with same labels go to the
+same
+sink operator sub-task. If this is not the case, samples may be written out-of-order, and be rejected by Prometheus.
+
+A `keyBy()` using the provided key selector,
+[`PrometheusTimeSeriesLabelsAndMetricNameKeySelector`](./src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusTimeSeriesLabelsAndMetricNameKeySelector.java),
+automatically partitions the time-series by labels.
+
+### Authentication and Request Signing (optional)
+
+The sink supports optional request-signing for authentication, implementing the
+[`PrometheusRequestSigner`](./src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusRequestSigner.java)
+interface.
+
+### Retriable errors
+
+The sink complies with Prometheus remote-write specs not retrying any request that return status codes `4xx`,
+except `429`,
+and retrying requests that return `5xx` or `429`, with an exponential backoff strategy.
+
+The retry strategy can be configured, as shown in the following snippet:
+
+```java
+PrometheusSink sink = PrometheusSink.builder()
+        .setRetryConfiguration(RetryConfiguration.builder()
+                .setInitialRetryDelayMS(30L)            // Initial retry delay (default: 30 ms)
+                .setMaxRetryDelayMS(5000L)              // Maximum retray delay, with exponential backoff (default: 5000 ms)
+                .setMaxRetryCount(100)                  // Max number of retries (default: 100)
+                .build())
+        // ...    
+        .build();
+```
+
+### Error handling behavior
+
+The behaviour of the sink, when an unrecoverable error happens while writing to Prometheus remote-write endpoint, is
+configurable.
+
+There are 3 error conditions:
+
+1. Prometheus returns a non-retriable error response (i.e. any `4xx` status code except `429`)
+2. Prometheus returns a retriable error response (i.e. `5xx` or `429`) but the max retry limit is exceeded
+3. The http client fails to complete the request, for an I/O error
+
+The default behavior of the sink, for all these three error conditions, is to `FAIL`: throw
+a `PrometheusSinkWriteException`, causing the job to fail.
+
+Optionally, for each of these error conditions independently, the sink can be configured to `DISCARD_AND_CONTINUE`: "
+log, discard the offending request and continue".
+
+The error handling behaviors can be configured when creating the instance of the sink, as shown in this snipped:
+
+```java
+PrometheusSink sink = PrometheusSink.builder()
+        // ...    
+        .setErrorHandlingBehaviourConfiguration(SinkWriterErrorHandlingBehaviorConfiguration.builder()
+                .onPrometheusNonRetriableError(OnErrorBehavior.DISCARD_AND_CONTINUE)
+                .onMaxRetryExceeded(OnErrorBehavior.DISCARD_AND_CONTINUE)
+                .onHttpClientIOFail(OnErrorBehavior.DISCARD_AND_CONTINUE)
+                .build())
+        .build();
+```
+
+When configured for `DISCARD_AND_CONTINUE`, the sink will do the following:
+
+1. Log a message at `WARN` level, with information about the problem and the number of time-series and samples dropped
+2. Update the counters (see below)
+3. Drop the offending write-request
+4. Continue with the next request
+
+Note that there is no partial-failure condition: the entire write-request is discarded regardless what data in the
+request are causing the problem.
+Prometheus does not return sufficient information to automatically handle partial requests.
+
+### Metrics
+
+The sink exposes custom metrics, counting the samples and write-requests (batches) successfully written or discarded.
+
+* `numSamplesOut` number of samples successfully written to Prometheus
+* `numWriteRequestsOut` number of write-requests successfully written to Prometheus
+* `numWriteRequestsRetries` number of write requests retried due to a retriable error (e.g. throttling)
+* `numSamplesDropped` number of samples dropped, for any reasons
+* `numSamplesNonRetriableDropped` (when `onPrometheusNonRetriableError` is set to `DISCARD_AND_CONTINUE`) number of
+  samples dropped due to non-retriable errors
+* `numSamplesRetryLimitDropped` (when `onMaxRetryExceeded` is set to `DISCARD_AND_CONTINUE`) number of samples dropped
+  due to reaching the max number of retries
+* `numWriteRequestsPermanentlyFailed` number of write requests permanently failed, due to any reasons (non retryable,
+  max nr of retries)
+
+**Note**: the `numBytesOut` does not measure the number of bytes, due to an internal limitation of the base sink.
+This metric should be ignored, and you should rely on `numSamplesOut` and `numWriteRequestsOut` instead.
+
+#### Metrics scope
+
+These custom metrics are exposed on partially customizable scope.
+By default, the scope is `Sink__Writer.Prometheus`. It can be customized to any `Sink__Writer.<metric-group>`.
+
+### Protobuf classes
+
+The connector includes classes for Protobuf
+objects, [Remote](src/main/java/org/apache/flink/connector/prometheus/sink/prometheus/Remote.java),
+[Types](src/main/java/org/apache/flink/connector/prometheus/sink/prometheus/Types.java),
+and [GoGoProtos](src/main/java/org/apache/flink/connector/prometheus/sink/protobuf/GoGoProtos.java).
+
+The [*.proto](src/main/proto) files are provided for reference only. Protobuf binaries are required to re-generate the
+Java classe.
+Please refer to [Protobuf documentation](https://protobuf.dev/getting-started/javatutorial/#compiling-protocol-buffers).
+
+### Example application
+
+You can find a complete application example using the connector in
+[DataStreamExample.java](src/test/java/org/apache/flink/connector/prometheus/sink/examples/DataStreamExample.java).
\ No newline at end of file
diff --git a/flink-connector-prometheus/pom.xml b/flink-connector-prometheus/pom.xml
new file mode 100644
index 0000000..6d19d91
--- /dev/null
+++ b/flink-connector-prometheus/pom.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.flink</groupId>
+        <artifactId>flink-connector-prometheus-parent</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+
+    <name>Flink : Connectors : Prometheus</name>
+    <groupId>org.apache.flink</groupId>
+    <artifactId>flink-connector-prometheus</artifactId>
+    <packaging>jar</packaging>
+
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.flink</groupId>
+            <artifactId>flink-streaming-java</artifactId>
+            <version>${flink.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.flink</groupId>
+            <artifactId>flink-connector-base</artifactId>
+            <version>${flink.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- protobuf, snappy - for prometheus remote-write payload -->
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+            <version>${protobuf.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java-util</artifactId>
+            <version>${protobuf.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.xerial.snappy</groupId>
+            <artifactId>snappy-java</artifactId>
+            <version>1.1.9.1</version>
+        </dependency>
+
+        <!-- http client -->
+        <dependency>
+            <groupId>org.apache.httpcomponents.client5</groupId>
+            <artifactId>httpclient5</artifactId>
+            <version>${apache.httpclient.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents.core5</groupId>
+            <artifactId>httpcore5</artifactId>
+            <version>${apache.httpclient.version}</version>
+        </dependency>
+
+        <!-- test -->
+        <dependency>
+            <groupId>org.apache.flink</groupId>
+            <artifactId>flink-test-utils-junit</artifactId>
+            <version>${flink.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.tomakehurst</groupId>
+            <artifactId>wiremock-jre8</artifactId>
+            <version>2.35.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <version>4.2.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.flink</groupId>
+            <artifactId>flink-connector-base</artifactId>
+            <version>${flink.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.flink</groupId>
+            <artifactId>flink-connector-test-utils</artifactId>
+            <version>${flink.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/HttpResponseCallback.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/HttpResponseCallback.java
new file mode 100644
index 0000000..3d96a25
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/HttpResponseCallback.java
@@ -0,0 +1,190 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.Internal;
+import org.apache.flink.connector.prometheus.sink.errorhandling.PrometheusSinkWriteException;
+import org.apache.flink.connector.prometheus.sink.http.RemoteWriteResponseClassifier;
+import org.apache.flink.connector.prometheus.sink.metrics.SinkMetricsCallback;
+import org.apache.flink.connector.prometheus.sink.prometheus.Types;
+
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Callback handling the outcome of the http async request.
+ *
+ * <p>This class implements the error handling behaviour, based on the configuration in {@link
+ * PrometheusSinkConfiguration.SinkWriterErrorHandlingBehaviorConfiguration}. Depending on the
+ * condition, the sink may throw an exception and cause the job to fail, or log the condition to
+ * WARN, increment the counters and continue with the next request.
+ *
+ * <p>In any case, every write-request either entirely succeed or fail. Partial failures are not
+ * handled.
+ *
+ * <p>In no condition a write-request is re-queued for the AsyncSink to reprocess: this would cause
+ * out of order writes that would be rejected by Prometheus.
+ *
+ * <p>Note that the http async client retries, based on the configured retry policy. The callback is
+ * called with an outcome of *completed* either when the request has succeeded or the max retry
+ * limit has been exceeded. It is responsibility of the callback distinguishing between these
+ * conditions.
+ */
+@Internal
+class HttpResponseCallback implements FutureCallback<SimpleHttpResponse> {
+    private static final Logger LOG = LoggerFactory.getLogger(HttpResponseCallback.class);
+
+    private final int timeSeriesCount;
+    private final long sampleCount;
+    private final Consumer<List<Types.TimeSeries>> reQueuedResult;
+    private final SinkMetricsCallback metricsCallback;
+    private final PrometheusSinkConfiguration.SinkWriterErrorHandlingBehaviorConfiguration
+            errorHandlingBehaviorConfig;
+
+    public HttpResponseCallback(
+            int timeSeriesCount,
+            long sampleCount,
+            SinkMetricsCallback metricsCallback,
+            PrometheusSinkConfiguration.SinkWriterErrorHandlingBehaviorConfiguration
+                    errorHandlingBehaviorConfig,
+            Consumer<List<Types.TimeSeries>> reQueuedResult) {
+        this.timeSeriesCount = timeSeriesCount;
+        this.sampleCount = sampleCount;
+        this.reQueuedResult = reQueuedResult;
+        this.metricsCallback = metricsCallback;
+        this.errorHandlingBehaviorConfig = errorHandlingBehaviorConfig;
+    }
+
+    /**
+     * The completed outcome is invoked every time the http client successfully receives a valid
+     * http response, regardless of the status code.
+     *
+     * <p>This method classifies the responses and implements the behaviour expected by the
+     * Remote-Write specifications. In case of error, the behaviour is determined by the error
+     * handling configuration.
+     */
+    @Override
+    public void completed(SimpleHttpResponse response) {
+        // Never re-queue requests
+        reQueuedResult.accept(Collections.emptyList());
+
+        // Success
+        if (RemoteWriteResponseClassifier.isSuccessResponse(response)) {
+            LOG.debug(
+                    "{},{} - successfully posted {} time-series, containing {} samples",
+                    response.getCode(),
+                    response.getReasonPhrase(),
+                    timeSeriesCount,
+                    sampleCount);
+            metricsCallback.onSuccessfulWriteRequest(sampleCount);
+            return;
+        }
+
+        String responseBody = response.getBodyText();
+        int statusCode = response.getCode();
+        String reasonPhrase = response.getReasonPhrase();
+
+        // Prometheus's response is a non-retriable error.
+        // Depending on the configured behaviour, log and discard or throw an exception
+        if (RemoteWriteResponseClassifier.isNonRetriableErrorResponse(response)) {
+            if (errorHandlingBehaviorConfig.getOnPrometheusNonRetriableError()
+                    == PrometheusSinkConfiguration.OnErrorBehavior.FAIL) {
+                throw new PrometheusSinkWriteException(
+                        "Non-retriable error response from Prometheus",
+                        statusCode,
+                        reasonPhrase,
+                        timeSeriesCount,
+                        sampleCount,
+                        responseBody);
+            }
+
+            LOG.warn(
+                    "{},{} {} (discarded {} time-series, containing {} samples)",
+                    statusCode,
+                    reasonPhrase,
+                    responseBody,
+                    timeSeriesCount,
+                    sampleCount);
+            metricsCallback.onFailedWriteRequestForNonRetriableError(sampleCount);
+            return;
+        }
+
+        // Retry limit exceeded on retriable error
+        // Depending on the configured behaviour, log and discard or throw an exception
+        if (RemoteWriteResponseClassifier.isRetriableErrorResponse(response)) {
+            if (errorHandlingBehaviorConfig.getOnMaxRetryExceeded()
+                    == PrometheusSinkConfiguration.OnErrorBehavior.FAIL) {
+                throw new PrometheusSinkWriteException(
+                        "Max retry limit exceeded on retriable error",
+                        statusCode,
+                        reasonPhrase,
+                        timeSeriesCount,
+                        sampleCount,
+                        responseBody);
+            }
+
+            LOG.warn(
+                    "{},{} {} (after retry limit reached, discarded {} time-series, containing {} samples)",
+                    statusCode,
+                    reasonPhrase,
+                    responseBody,
+                    timeSeriesCount,
+                    sampleCount);
+            metricsCallback.onFailedWriteRequestForRetryLimitExceeded(sampleCount);
+            return;
+        }
+
+        // Unexpected/unhandled response outcome
+        throw new PrometheusSinkWriteException(
+                "Unexpected status code returned from the remote-write endpoint",
+                statusCode,
+                reasonPhrase,
+                timeSeriesCount,
+                sampleCount,
+                responseBody);
+    }
+
+    @Override
+    public void failed(Exception ex) {
+        // General I/O failure reported by http client
+        // Depending on the configured behavior, throw an exception or log and discard
+        if (errorHandlingBehaviorConfig.getOnHttpClientIOFail()
+                == PrometheusSinkConfiguration.OnErrorBehavior.FAIL) {
+            throw new PrometheusSinkWriteException("Http client failure", ex);
+        } else {
+            LOG.warn(
+                    "Exception executing the remote-write (discarded {} time-series containing {} samples)",
+                    timeSeriesCount,
+                    sampleCount,
+                    ex);
+            metricsCallback.onFailedWriteRequestForHttpClientIoFail(sampleCount);
+        }
+    }
+
+    @Override
+    public void cancelled() {
+        // When the async http client is cancelled, the sink always throws an exception
+        throw new PrometheusSinkWriteException("Write request execution cancelled");
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusRemoteWriteHttpRequestBuilder.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusRemoteWriteHttpRequestBuilder.java
new file mode 100644
index 0000000..5473932
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusRemoteWriteHttpRequestBuilder.java
@@ -0,0 +1,79 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.Internal;
+import org.apache.flink.util.Preconditions;
+
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpHeaders;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Builds the POST request to the Remote-Write endpoint for a given binary payload. */
+@Internal
+public class PrometheusRemoteWriteHttpRequestBuilder {
+
+    private static final ContentType CONTENT_TYPE = ContentType.create("application/x-protobuf");
+
+    private static final String CONTENT_ENCODING = "snappy";
+    private static final String REMOTE_WRITE_VERSION_HEADER = "X-Prometheus-Remote-Write-Version";
+    private static final String REMOTE_WRITE_VERSION = "0.1.0";
+
+    public static final String DEFAULT_USER_AGENT = "Flink-Prometheus";
+
+    private final String prometheusRemoteWriteUrl;
+    private final PrometheusRequestSigner requestSigner;
+
+    private final Map<String, String> fixedHeaders;
+
+    public PrometheusRemoteWriteHttpRequestBuilder(
+            String prometheusRemoteWriteUrl,
+            PrometheusRequestSigner requestSigner,
+            String httpUserAgent) {
+        Preconditions.checkNotNull(httpUserAgent, "User-Agent not specified");
+
+        this.prometheusRemoteWriteUrl = prometheusRemoteWriteUrl;
+        this.requestSigner = requestSigner;
+        this.fixedHeaders = new HashMap<>();
+        fixedHeaders.put(HttpHeaders.CONTENT_ENCODING, CONTENT_ENCODING);
+        fixedHeaders.put(REMOTE_WRITE_VERSION_HEADER, REMOTE_WRITE_VERSION);
+        fixedHeaders.put(HttpHeaders.USER_AGENT, httpUserAgent);
+    }
+
+    public SimpleHttpRequest buildHttpRequest(byte[] httpRequestBody) {
+        Map<String, String> headers = new HashMap<>(fixedHeaders);
+        if (requestSigner != null) {
+            requestSigner.addSignatureHeaders(headers, httpRequestBody);
+        }
+
+        SimpleRequestBuilder builder =
+                SimpleRequestBuilder.post()
+                        .setUri(prometheusRemoteWriteUrl)
+                        .setBody(httpRequestBody, CONTENT_TYPE);
+
+        for (Map.Entry<String, String> header : headers.entrySet()) {
+            builder.addHeader(header.getKey(), header.getValue());
+        }
+
+        return builder.build();
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusRequestSigner.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusRequestSigner.java
new file mode 100644
index 0000000..564f44d
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusRequestSigner.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.PublicEvolving;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * Interface for a request signer, specific of the Prometheus implementation.
+ *
+ * <p>A request signer implementation can generate additional Http request headers, based on the
+ * existing headers and the request body.
+ *
+ * <p>The signature method is called on every write request. For performance reasons, the Map of
+ * HTTP headers is mutated, instead of making a copy.
+ */
+@PublicEvolving
+public interface PrometheusRequestSigner extends Serializable {
+
+    /**
+     * Add to the existing http request headers any additional header required by the specific
+     * Prometheus implementation for signing.
+     *
+     * @param requestHeaders original Http request headers. For efficiency, the implementation is
+     *     expected to modify the Map in place. The Map is expected to be mutable.
+     * @param requestBody request body, already compressed.
+     */
+    void addSignatureHeaders(Map<String, String> requestHeaders, byte[] requestBody);
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSink.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSink.java
new file mode 100644
index 0000000..52a10ba
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSink.java
@@ -0,0 +1,150 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.PublicEvolving;
+import org.apache.flink.connector.base.sink.AsyncSinkBase;
+import org.apache.flink.connector.base.sink.writer.BufferedRequestState;
+import org.apache.flink.connector.base.sink.writer.ElementConverter;
+import org.apache.flink.connector.prometheus.sink.http.PrometheusAsyncHttpClientBuilder;
+import org.apache.flink.connector.prometheus.sink.metrics.SinkMetrics;
+import org.apache.flink.connector.prometheus.sink.metrics.SinkMetricsCallback;
+import org.apache.flink.connector.prometheus.sink.prometheus.Types;
+import org.apache.flink.core.io.SimpleVersionedSerializer;
+import org.apache.flink.util.Preconditions;
+
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+
+import java.util.Collection;
+
+/** Sink implementation accepting {@link PrometheusTimeSeries} as inputs. */
+@PublicEvolving
+public class PrometheusSink extends AsyncSinkBase<PrometheusTimeSeries, Types.TimeSeries> {
+    private final String prometheusRemoteWriteUrl;
+    private final PrometheusAsyncHttpClientBuilder clientBuilder;
+    private final PrometheusRequestSigner requestSigner;
+    private final int maxBatchSizeInSamples;
+    private final String httpUserAgent;
+    private final PrometheusSinkConfiguration.SinkWriterErrorHandlingBehaviorConfiguration
+            errorHandlingBehaviorConfig;
+    private final String metricGroupName;
+
+    @SuppressWarnings("checkstyle:RegexpSingleline")
+    protected PrometheusSink(
+            ElementConverter<PrometheusTimeSeries, Types.TimeSeries> elementConverter,
+            int maxInFlightRequests,
+            int maxBufferedRequests,
+            int maxBatchSizeInSamples,
+            int maxRecordSizeInSamples,
+            long maxTimeInBufferMS,
+            String prometheusRemoteWriteUrl,
+            PrometheusAsyncHttpClientBuilder clientBuilder,
+            PrometheusRequestSigner requestSigner,
+            String httpUserAgent,
+            PrometheusSinkConfiguration.SinkWriterErrorHandlingBehaviorConfiguration
+                    errorHandlingBehaviorConfig,
+            String metricGroupName) {
+        // This sink batches in terms of "samples", because writes to Prometheus are better
+        // optimized in terms of samples. AsyncSinkBase handles batching and does not make any
+        // assumptions about the actual unit of "size", but parameters are named assuming this unit
+        // is "bytes".
+        super(
+                elementConverter,
+                maxBatchSizeInSamples, // maxBatchSize,
+                maxInFlightRequests,
+                maxBufferedRequests,
+                maxBatchSizeInSamples, // maxBatchSizeInBytes,
+                maxTimeInBufferMS,
+                maxRecordSizeInSamples // maxRecordSizeInBytes
+                );
+
+        Preconditions.checkArgument(maxInFlightRequests == 1, "maxInFlightRequests must be 1");
+
+        this.maxBatchSizeInSamples = maxBatchSizeInSamples;
+        this.requestSigner = requestSigner;
+        this.prometheusRemoteWriteUrl = prometheusRemoteWriteUrl;
+        this.clientBuilder = clientBuilder;
+        this.httpUserAgent = httpUserAgent;
+        this.errorHandlingBehaviorConfig = errorHandlingBehaviorConfig;
+        this.metricGroupName = metricGroupName;
+    }
+
+    @Override
+    public StatefulSinkWriter<PrometheusTimeSeries, BufferedRequestState<Types.TimeSeries>>
+            createWriter(InitContext initContext) {
+        SinkMetricsCallback metricsCallback =
+                new SinkMetricsCallback(
+                        SinkMetrics.registerSinkMetrics(
+                                initContext.metricGroup().addGroup(metricGroupName)));
+        CloseableHttpAsyncClient asyncHttpClient =
+                clientBuilder.buildAndStartClient(metricsCallback);
+
+        return new PrometheusSinkWriter(
+                getElementConverter(),
+                initContext,
+                getMaxInFlightRequests(),
+                getMaxBufferedRequests(),
+                maxBatchSizeInSamples,
+                getMaxRecordSizeInBytes(),
+                getMaxTimeInBufferMS(),
+                prometheusRemoteWriteUrl,
+                asyncHttpClient,
+                metricsCallback,
+                requestSigner,
+                httpUserAgent,
+                errorHandlingBehaviorConfig);
+    }
+
+    @Override
+    public StatefulSinkWriter<PrometheusTimeSeries, BufferedRequestState<Types.TimeSeries>>
+            restoreWriter(
+                    InitContext initContext,
+                    Collection<BufferedRequestState<Types.TimeSeries>> recoveredState) {
+        SinkMetricsCallback metricsCallback =
+                new SinkMetricsCallback(
+                        SinkMetrics.registerSinkMetrics(
+                                initContext.metricGroup().addGroup(metricGroupName)));
+        CloseableHttpAsyncClient asyncHttpClient =
+                clientBuilder.buildAndStartClient(metricsCallback);
+        return new PrometheusSinkWriter(
+                getElementConverter(),
+                initContext,
+                getMaxInFlightRequests(),
+                getMaxBufferedRequests(),
+                maxBatchSizeInSamples,
+                getMaxRecordSizeInBytes(),
+                getMaxTimeInBufferMS(),
+                prometheusRemoteWriteUrl,
+                asyncHttpClient,
+                metricsCallback,
+                requestSigner,
+                httpUserAgent,
+                errorHandlingBehaviorConfig,
+                recoveredState);
+    }
+
+    public static PrometheusSinkBuilder builder() {
+        return new PrometheusSinkBuilder();
+    }
+
+    @Override
+    public SimpleVersionedSerializer<BufferedRequestState<Types.TimeSeries>>
+            getWriterStateSerializer() {
+        return new PrometheusStateSerializer();
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSinkBuilder.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSinkBuilder.java
new file mode 100644
index 0000000..69eb72c
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSinkBuilder.java
@@ -0,0 +1,243 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.PublicEvolving;
+import org.apache.flink.connector.base.sink.AsyncSinkBase;
+import org.apache.flink.connector.base.sink.AsyncSinkBaseBuilder;
+import org.apache.flink.connector.prometheus.sink.http.PrometheusAsyncHttpClientBuilder;
+import org.apache.flink.connector.prometheus.sink.prometheus.Types;
+import org.apache.flink.util.Preconditions;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Optional;
+
+/** Builder for Sink implementation. */
+@PublicEvolving
+public class PrometheusSinkBuilder
+        extends AsyncSinkBaseBuilder<
+                PrometheusTimeSeries, Types.TimeSeries, PrometheusSinkBuilder> {
+    private static final Logger LOG = LoggerFactory.getLogger(PrometheusSinkBuilder.class);
+
+    // Max batch size, in number of samples
+    private static final int DEFAULT_MAX_BATCH_SIZE_IN_SAMPLES = 500;
+    // Max time a record is buffered
+    private static final long DEFAULT_MAX_TIME_IN_BUFFER_MS = 5000;
+    // Max nr of requestEntry that will be buffered
+    private static final int DEFAULT_MAX_BUFFERED_REQUESTS = 1000;
+    // Metric Group name added to the custom metrics
+    private static final String DEFAULT_METRIC_GROUP_NAME = "Prometheus";
+
+    // Max in-flight requests is always = 1, to retain ordering
+    private static final int MAX_IN_FLIGHT_REQUESTS = 1;
+
+    private String prometheusRemoteWriteUrl;
+    private PrometheusSinkConfiguration.RetryConfiguration retryConfiguration;
+    private Integer socketTimeoutMs;
+    private PrometheusRequestSigner requestSigner = null;
+    private Integer maxBatchSizeInSamples;
+    private Integer maxRecordSizeInSamples;
+    private String httpUserAgent = null;
+    private PrometheusSinkConfiguration.SinkWriterErrorHandlingBehaviorConfiguration
+            errorHandlingBehaviorConfig = null;
+    private String metricGroupName = null;
+
+    @Override
+    public AsyncSinkBase<PrometheusTimeSeries, Types.TimeSeries> build() {
+
+        int actualMaxBatchSizeInSamples =
+                Optional.ofNullable(maxBatchSizeInSamples)
+                        .orElse(DEFAULT_MAX_BATCH_SIZE_IN_SAMPLES);
+        int actualMaxBufferedRequests =
+                Optional.ofNullable(getMaxBufferedRequests()).orElse(DEFAULT_MAX_BUFFERED_REQUESTS);
+        long actualMaxTimeInBufferMS =
+                Optional.ofNullable(getMaxTimeInBufferMS()).orElse(DEFAULT_MAX_TIME_IN_BUFFER_MS);
+
+        int actualMaxRecordSizeInSamples =
+                Optional.ofNullable(maxRecordSizeInSamples).orElse(maxBatchSizeInSamples);
+
+        int actualSocketTimeoutMs =
+                Optional.ofNullable(socketTimeoutMs)
+                        .orElse(PrometheusAsyncHttpClientBuilder.DEFAULT_SOCKET_TIMEOUT_MS);
+
+        String actualHttpUserAgent =
+                Optional.ofNullable(httpUserAgent)
+                        .orElse(PrometheusRemoteWriteHttpRequestBuilder.DEFAULT_USER_AGENT);
+
+        PrometheusSinkConfiguration.SinkWriterErrorHandlingBehaviorConfiguration
+                actualErrorHandlingBehaviorConfig =
+                        Optional.ofNullable(errorHandlingBehaviorConfig)
+                                .orElse(
+                                        PrometheusSinkConfiguration
+                                                .SinkWriterErrorHandlingBehaviorConfiguration
+                                                .DEFAULT_BEHAVIORS);
+
+        String actualMetricGroupName =
+                Optional.ofNullable(metricGroupName).orElse(DEFAULT_METRIC_GROUP_NAME);
+
+        Preconditions.checkArgument(
+                StringUtils.isNotBlank(prometheusRemoteWriteUrl),
+                "Missing or blank Prometheus Remote-Write URL");
+        checkValidRemoteWriteUrl(prometheusRemoteWriteUrl);
+        Preconditions.checkNotNull(retryConfiguration, "Missing retry configuration");
+        Preconditions.checkArgument(
+                actualMaxBatchSizeInSamples > 0, "Max batch size (in samples) must be positive");
+        Preconditions.checkArgument(
+                actualMaxRecordSizeInSamples <= actualMaxBatchSizeInSamples,
+                "Max record size (in samples) must be <= Max batch size");
+
+        LOG.info(
+                "Prometheus sink configuration:"
+                        + "\n\t\tmaxBatchSizeInSamples={}\n\t\tmaxRecordSizeInSamples={}"
+                        + "\n\t\tmaxTimeInBufferMs={}\n\t\tmaxInFlightRequests={}\n\t\tmaxBufferedRequests={}"
+                        + "\n\t\tinitialRetryDelayMs={}\n\t\tmaxRetryDelayMs={}\n\t\tmaxRetryCount={}"
+                        + "\n\t\tsocketTimeoutMs={}\n\t\thttpUserAgent={}"
+                        + "\n\t\tErrorHandlingBehaviour: onMaxRetryExceeded={}, onHttpClientIOFailure={}, onNonRetriableError={}",
+                actualMaxBatchSizeInSamples,
+                actualMaxRecordSizeInSamples,
+                actualMaxTimeInBufferMS,
+                MAX_IN_FLIGHT_REQUESTS,
+                actualMaxBufferedRequests,
+                retryConfiguration.getInitialRetryDelayMS(),
+                retryConfiguration.getMaxRetryDelayMS(),
+                retryConfiguration.getMaxRetryCount(),
+                socketTimeoutMs,
+                actualHttpUserAgent,
+                actualErrorHandlingBehaviorConfig.getOnMaxRetryExceeded(),
+                actualErrorHandlingBehaviorConfig.getOnHttpClientIOFail(),
+                actualErrorHandlingBehaviorConfig.getOnPrometheusNonRetriableError());
+
+        return new PrometheusSink(
+                new PrometheusTimeSeriesConverter(),
+                MAX_IN_FLIGHT_REQUESTS,
+                actualMaxBufferedRequests,
+                actualMaxBatchSizeInSamples,
+                actualMaxRecordSizeInSamples,
+                actualMaxTimeInBufferMS,
+                prometheusRemoteWriteUrl,
+                new PrometheusAsyncHttpClientBuilder(retryConfiguration)
+                        .setSocketTimeout(actualSocketTimeoutMs),
+                requestSigner,
+                actualHttpUserAgent,
+                actualErrorHandlingBehaviorConfig,
+                actualMetricGroupName);
+    }
+
+    private static void checkValidRemoteWriteUrl(String url) {
+        try {
+            new URL(url);
+        } catch (MalformedURLException mue) {
+            throw new IllegalArgumentException("Invalid Remote-Write URL: " + url, mue);
+        }
+    }
+
+    public PrometheusSinkBuilder setPrometheusRemoteWriteUrl(String prometheusRemoteWriteUrl) {
+        this.prometheusRemoteWriteUrl = prometheusRemoteWriteUrl;
+        return this;
+    }
+
+    public PrometheusSinkBuilder setRequestSigner(PrometheusRequestSigner requestSigner) {
+        this.requestSigner = requestSigner;
+        return this;
+    }
+
+    public PrometheusSinkBuilder setMaxBatchSizeInSamples(int maxBatchSizeInSamples) {
+        this.maxBatchSizeInSamples = maxBatchSizeInSamples;
+        return this;
+    }
+
+    public PrometheusSinkBuilder setMaxRecordSizeInSamples(int maxRecordSizeInSamples) {
+        this.maxRecordSizeInSamples = maxRecordSizeInSamples;
+        return this;
+    }
+
+    public PrometheusSinkBuilder setRetryConfiguration(
+            PrometheusSinkConfiguration.RetryConfiguration retryConfiguration) {
+        this.retryConfiguration = retryConfiguration;
+        return this;
+    }
+
+    public PrometheusSinkBuilder setSocketTimeoutMs(int socketTimeoutMs) {
+        this.socketTimeoutMs = socketTimeoutMs;
+        return this;
+    }
+
+    public PrometheusSinkBuilder setHttpUserAgent(String httpUserAgent) {
+        this.httpUserAgent = httpUserAgent;
+        return this;
+    }
+
+    public PrometheusSinkBuilder setErrorHandlingBehaviourConfiguration(
+            PrometheusSinkConfiguration.SinkWriterErrorHandlingBehaviorConfiguration
+                    errorHandlingBehaviorConfig) {
+        this.errorHandlingBehaviorConfig = errorHandlingBehaviorConfig;
+        return this;
+    }
+
+    public PrometheusSinkBuilder setMetricGroupName(String metricGroupName) {
+        this.metricGroupName = metricGroupName;
+        return this;
+    }
+
+    /// Disable accessing maxBatchSize, maxBatchSizeInBytes, and maxRecordSizeInBytes directly
+
+    /** Not supported. Use setMaxBatchSizeInSamples(int) instead */
+    @Override
+    public PrometheusSinkBuilder setMaxBatchSize(int maxBatchSize) {
+        throw new UnsupportedOperationException("maxBatchSize is not supported by this sink");
+    }
+
+    /** Not supported. Use setMaxBatchSizeInSamples(int) instead */
+    @Override
+    public PrometheusSinkBuilder setMaxBatchSizeInBytes(long maxBatchSizeInBytes) {
+        throw new UnsupportedOperationException(
+                "maxBatchSizeInBytes is not supported by this sink");
+    }
+
+    /** Not supported. Use setMaxRecordSizeInSamples(int) instead */
+    @Override
+    public PrometheusSinkBuilder setMaxRecordSizeInBytes(long maxRecordSizeInBytes) {
+        throw new UnsupportedOperationException(
+                "maxRecordSizeInBytes is not supported by this sink");
+    }
+
+    /** Not supported. Use getMaxBatchSizeInSamples() instead */
+    @Override
+    protected Integer getMaxBatchSize() {
+        throw new UnsupportedOperationException("maxBatchSize is not supported by this sink");
+    }
+
+    /** Not supported. Use getMaxBatchSizeInSamples() instead */
+    @Override
+    protected Long getMaxBatchSizeInBytes() {
+        throw new UnsupportedOperationException(
+                "maxRecordSizeInBytes is not supported by this sink");
+    }
+
+    /** Not supported. Use getMaxRecordSizeInSamples() instead */
+    @Override
+    protected Long getMaxRecordSizeInBytes() {
+        throw new UnsupportedOperationException(
+                "maxRecordSizeInBytes is not supported by this sink");
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSinkConfiguration.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSinkConfiguration.java
new file mode 100644
index 0000000..b939d09
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSinkConfiguration.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.PublicEvolving;
+
+import java.io.Serializable;
+import java.util.Optional;
+
+import static org.apache.flink.connector.prometheus.sink.PrometheusSinkConfiguration.OnErrorBehavior.FAIL;
+
+/** This class contains configuration classes for different components of the Prometheus sink. */
+@PublicEvolving
+public class PrometheusSinkConfiguration {
+
+    /**
+     * Defines the behaviour when an error is encountered: discard the offending request and
+     * continue, or fail, throwing an exception.
+     */
+    public enum OnErrorBehavior {
+        DISCARD_AND_CONTINUE,
+        FAIL
+    }
+
+    /**
+     * Configure the error-handling behavior of the writer, for different types of error. Also
+     * defines default behaviors.
+     */
+    public static class SinkWriterErrorHandlingBehaviorConfiguration implements Serializable {
+
+        public static final OnErrorBehavior ON_MAX_RETRY_EXCEEDED_DEFAULT_BEHAVIOR = FAIL;
+        public static final OnErrorBehavior ON_HTTP_CLIENT_IO_FAIL_DEFAULT_BEHAVIOR = FAIL;
+        public static final OnErrorBehavior ON_PROMETHEUS_NON_RETRIABLE_ERROR_DEFAULT_BEHAVIOR =
+                FAIL;
+
+        /** Behaviour when the max retries is exceeded on Prometheus retriable errors. */
+        private final OnErrorBehavior onMaxRetryExceeded;
+
+        /** Behaviour when the HTTP client fails, for an I/O problem. */
+        private final OnErrorBehavior onHttpClientIOFail;
+
+        /** Behaviour when Prometheus Remote-Write respond with a non-retriable error. */
+        private final OnErrorBehavior onPrometheusNonRetriableError;
+
+        public SinkWriterErrorHandlingBehaviorConfiguration(
+                OnErrorBehavior onMaxRetryExceeded,
+                OnErrorBehavior onHttpClientIOFail,
+                OnErrorBehavior onPrometheusNonRetriableError) {
+            this.onMaxRetryExceeded = onMaxRetryExceeded;
+            this.onHttpClientIOFail = onHttpClientIOFail;
+            this.onPrometheusNonRetriableError = onPrometheusNonRetriableError;
+        }
+
+        public OnErrorBehavior getOnMaxRetryExceeded() {
+            return onMaxRetryExceeded;
+        }
+
+        public OnErrorBehavior getOnHttpClientIOFail() {
+            return onHttpClientIOFail;
+        }
+
+        public OnErrorBehavior getOnPrometheusNonRetriableError() {
+            return onPrometheusNonRetriableError;
+        }
+
+        /** Builder for PrometheusSinkWriterErrorHandlingConfiguration. */
+        public static class Builder {
+            private OnErrorBehavior onMaxRetryExceeded = null;
+            private OnErrorBehavior onHttpClientIOFail = null;
+            private OnErrorBehavior onPrometheusNonRetriableError = null;
+
+            public Builder() {}
+
+            public Builder onMaxRetryExceeded(OnErrorBehavior onErrorBehavior) {
+                this.onMaxRetryExceeded = onErrorBehavior;
+                return this;
+            }
+
+            public Builder onHttpClientIOFail(OnErrorBehavior onErrorBehavior) {
+                this.onHttpClientIOFail = onErrorBehavior;
+                return this;
+            }
+
+            public Builder onPrometheusNonRetriableError(OnErrorBehavior onErrorBehavior) {
+                this.onPrometheusNonRetriableError = onErrorBehavior;
+                return this;
+            }
+
+            public SinkWriterErrorHandlingBehaviorConfiguration build() {
+                return new SinkWriterErrorHandlingBehaviorConfiguration(
+                        Optional.ofNullable(onMaxRetryExceeded)
+                                .orElse(ON_MAX_RETRY_EXCEEDED_DEFAULT_BEHAVIOR),
+                        Optional.ofNullable(onHttpClientIOFail)
+                                .orElse(ON_HTTP_CLIENT_IO_FAIL_DEFAULT_BEHAVIOR),
+                        Optional.ofNullable(onPrometheusNonRetriableError)
+                                .orElse(ON_PROMETHEUS_NON_RETRIABLE_ERROR_DEFAULT_BEHAVIOR));
+            }
+        }
+
+        public static Builder builder() {
+            return new Builder();
+        }
+
+        public static final SinkWriterErrorHandlingBehaviorConfiguration DEFAULT_BEHAVIORS =
+                builder().build();
+    }
+
+    /** Defines the retry strategy configuration. */
+    public static class RetryConfiguration implements Serializable {
+        public static final long DEFAULT_INITIAL_RETRY_DELAY_MS = 30L;
+        public static final long DEFAULT_MAX_RETRY_DELAY_MS = 5000L;
+        public static final int DEFAULT_MAX_RETRY_COUNT = 100;
+
+        private final long initialRetryDelayMS;
+        private final long maxRetryDelayMS;
+        private final int maxRetryCount;
+
+        public long getInitialRetryDelayMS() {
+            return initialRetryDelayMS;
+        }
+
+        public long getMaxRetryDelayMS() {
+            return maxRetryDelayMS;
+        }
+
+        public int getMaxRetryCount() {
+            return maxRetryCount;
+        }
+
+        public RetryConfiguration(
+                long initialRetryDelayMS, long maxRetryDelayMS, int maxRetryCount) {
+            this.initialRetryDelayMS = initialRetryDelayMS;
+            this.maxRetryDelayMS = maxRetryDelayMS;
+            this.maxRetryCount = maxRetryCount;
+        }
+
+        public static Builder builder() {
+            return new Builder();
+        }
+
+        /** Builder. */
+        public static class Builder {
+            private long initialRetryDelayMS = DEFAULT_INITIAL_RETRY_DELAY_MS;
+            private long maxRetryDelayMS = DEFAULT_MAX_RETRY_DELAY_MS;
+            private int maxRetryCount = DEFAULT_MAX_RETRY_COUNT;
+
+            public Builder setInitialRetryDelayMS(long initialRetryDelayMS) {
+                this.initialRetryDelayMS = initialRetryDelayMS;
+                return this;
+            }
+
+            public Builder setMaxRetryDelayMS(long maxRetryDelayMS) {
+                this.maxRetryDelayMS = maxRetryDelayMS;
+                return this;
+            }
+
+            public Builder setMaxRetryCount(int maxRetryCount) {
+                this.maxRetryCount = maxRetryCount;
+                return this;
+            }
+
+            public RetryConfiguration build() {
+                return new RetryConfiguration(initialRetryDelayMS, maxRetryDelayMS, maxRetryCount);
+            }
+        }
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSinkException.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSinkException.java
new file mode 100644
index 0000000..b8b3d4c
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSinkException.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.PublicEvolving;
+
+/**
+ * A {@link RuntimeException} wrapper indicating a generic unhandled exception was thrown by the
+ * Prometheus sink.
+ */
+@PublicEvolving
+public class PrometheusSinkException extends RuntimeException {
+    public PrometheusSinkException(String message) {
+        super(message);
+    }
+
+    public PrometheusSinkException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSinkWriter.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSinkWriter.java
new file mode 100644
index 0000000..f06f451
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusSinkWriter.java
@@ -0,0 +1,200 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.Internal;
+import org.apache.flink.api.connector.sink2.Sink;
+import org.apache.flink.connector.base.sink.writer.AsyncSinkWriter;
+import org.apache.flink.connector.base.sink.writer.BufferedRequestState;
+import org.apache.flink.connector.base.sink.writer.ElementConverter;
+import org.apache.flink.connector.base.sink.writer.config.AsyncSinkWriterConfiguration;
+import org.apache.flink.connector.prometheus.sink.metrics.SinkMetricsCallback;
+import org.apache.flink.connector.prometheus.sink.prometheus.Remote;
+import org.apache.flink.connector.prometheus.sink.prometheus.Types;
+
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.core5.io.CloseMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xerial.snappy.Snappy;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Writer, taking care of batching the {@link PrometheusTimeSeries} and handling retries.
+ *
+ * <p>The batching of this sink is in terms of Samples, not bytes. The goal is adaptively increase
+ * the number of Samples in each batch, a WriteRequest sent to Prometheus, to a configurable number.
+ * This is the parameter maxBatchSizeInBytes.
+ *
+ * <p>getSizeInBytes(requestEntry) returns the number of Samples (not bytes) and maxBatchSizeInBytes
+ * is actually in terms of Samples (not bytes).
+ *
+ * <p>In AsyncSinkWriter, maxBatchSize is in terms of requestEntries (TimeSeries). But because each
+ * TimeSeries contains 1+ Samples, we set maxBatchSize = maxBatchSizeInBytes.
+ *
+ * <p>maxRecordSizeInBytes is also calculated in the same unit assumed by getSizeInBytes(...). In
+ * our case is the max number of Samples in a single TimeSeries sent to the Sink. We are limiting
+ * the number of Samples in each TimeSeries to the max batch size, setting maxRecordSizeInBytes =
+ * maxBatchSizeInBytes.
+ */
+@Internal
+public class PrometheusSinkWriter extends AsyncSinkWriter<PrometheusTimeSeries, Types.TimeSeries> {
+    private static final Logger LOG = LoggerFactory.getLogger(PrometheusSinkWriter.class);
+
+    private final SinkMetricsCallback metricsCallback;
+    private final CloseableHttpAsyncClient asyncHttpClient;
+    private final PrometheusRemoteWriteHttpRequestBuilder requestBuilder;
+    private final PrometheusSinkConfiguration.SinkWriterErrorHandlingBehaviorConfiguration
+            errorHandlingBehaviorConfig;
+
+    public PrometheusSinkWriter(
+            ElementConverter<PrometheusTimeSeries, Types.TimeSeries> elementConverter,
+            Sink.InitContext context,
+            int maxInFlightRequests,
+            int maxBufferedRequests,
+            int maxBatchSizeInSamples,
+            long maxRecordSizeInSamples,
+            long maxTimeInBufferMS,
+            String prometheusRemoteWriteUrl,
+            CloseableHttpAsyncClient asyncHttpClient,
+            SinkMetricsCallback metricsCallback,
+            PrometheusRequestSigner requestSigner,
+            String httpUserAgent,
+            PrometheusSinkConfiguration.SinkWriterErrorHandlingBehaviorConfiguration
+                    errorHandlingBehaviorConfig) {
+        this(
+                elementConverter,
+                context,
+                maxInFlightRequests,
+                maxBufferedRequests,
+                maxBatchSizeInSamples,
+                maxRecordSizeInSamples,
+                maxTimeInBufferMS,
+                prometheusRemoteWriteUrl,
+                asyncHttpClient,
+                metricsCallback,
+                requestSigner,
+                httpUserAgent,
+                errorHandlingBehaviorConfig,
+                Collections.emptyList());
+    }
+
+    public PrometheusSinkWriter(
+            ElementConverter<PrometheusTimeSeries, Types.TimeSeries> elementConverter,
+            Sink.InitContext context,
+            int maxInFlightRequests,
+            int maxBufferedRequests,
+            int maxBatchSizeInSamples,
+            long maxRecordSizeInSamples,
+            long maxTimeInBufferMS,
+            String prometheusRemoteWriteUrl,
+            CloseableHttpAsyncClient asyncHttpClient,
+            SinkMetricsCallback metricsCallback,
+            PrometheusRequestSigner requestSigner,
+            String httpUserAgent,
+            PrometheusSinkConfiguration.SinkWriterErrorHandlingBehaviorConfiguration
+                    errorHandlingBehaviorConfig,
+            Collection<BufferedRequestState<Types.TimeSeries>> states) {
+        super(
+                elementConverter,
+                context,
+                AsyncSinkWriterConfiguration.builder()
+                        .setMaxBatchSize(maxBatchSizeInSamples)
+                        .setMaxBatchSizeInBytes(maxBatchSizeInSamples)
+                        .setMaxInFlightRequests(maxInFlightRequests)
+                        .setMaxBufferedRequests(maxBufferedRequests)
+                        .setMaxTimeInBufferMS(maxTimeInBufferMS)
+                        .setMaxRecordSizeInBytes(maxRecordSizeInSamples)
+                        .build(),
+                states);
+        this.requestBuilder =
+                new PrometheusRemoteWriteHttpRequestBuilder(
+                        prometheusRemoteWriteUrl, requestSigner, httpUserAgent);
+        this.asyncHttpClient = asyncHttpClient;
+        this.metricsCallback = metricsCallback;
+        this.errorHandlingBehaviorConfig = errorHandlingBehaviorConfig;
+    }
+
+    /**
+     * This is the "size" of the request entry (a {@link Types.TimeSeries time-series}) used for
+     * batching. Regardless the name of the method, it returns the number of {@link Types.Sample
+     * samples} in the time-series (not bytes), to support batching in terms of samples.
+     *
+     * @param timeSeries the request entry for which we want to know the size
+     * @return number of samples in the time-series
+     */
+    @Override
+    protected long getSizeInBytes(Types.TimeSeries timeSeries) {
+        return RequestEntrySizeUtils.requestSizeForBatching(timeSeries);
+    }
+
+    @Override
+    protected void submitRequestEntries(
+            List<Types.TimeSeries> requestEntries, Consumer<List<Types.TimeSeries>> requestResult) {
+        int timeSeriesCount = requestEntries.size();
+        long sampleCount = RequestEntrySizeUtils.countSamples(requestEntries);
+        if (LOG.isTraceEnabled()) {
+            LOG.trace(
+                    "Writing {} time-series containing {} samples ", timeSeriesCount, sampleCount);
+        }
+
+        Remote.WriteRequest writeRequest = buildWriteRequest(requestEntries);
+        byte[] requestBody;
+        try {
+            requestBody = compressWriteRequest(writeRequest);
+        } catch (IOException e) {
+            throw new PrometheusSinkException("Exception compressing the request body", e);
+        }
+
+        SimpleHttpRequest postRequest = requestBuilder.buildHttpRequest(requestBody);
+        asyncHttpClient.execute(
+                postRequest,
+                new HttpResponseCallback(
+                        timeSeriesCount,
+                        sampleCount,
+                        metricsCallback,
+                        errorHandlingBehaviorConfig,
+                        requestResult));
+    }
+
+    @Override
+    public void close() {
+        if (this.asyncHttpClient != null) {
+            this.asyncHttpClient.close(CloseMode.GRACEFUL);
+        }
+        super.close();
+    }
+
+    private Remote.WriteRequest buildWriteRequest(List<Types.TimeSeries> requestEntries) {
+        Remote.WriteRequest.Builder builder = Remote.WriteRequest.newBuilder();
+        for (Types.TimeSeries timeSeries : requestEntries) {
+            builder.addTimeseries(timeSeries);
+        }
+        return builder.build();
+    }
+
+    private byte[] compressWriteRequest(Remote.WriteRequest writeRequest) throws IOException {
+        return Snappy.compress(writeRequest.toByteArray());
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusStateSerializer.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusStateSerializer.java
new file mode 100644
index 0000000..f14a71b
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusStateSerializer.java
@@ -0,0 +1,156 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.Internal;
+import org.apache.flink.connector.base.sink.writer.AsyncSinkWriterStateSerializer;
+import org.apache.flink.connector.base.sink.writer.BufferedRequestState;
+import org.apache.flink.connector.base.sink.writer.RequestEntryWrapper;
+import org.apache.flink.connector.prometheus.sink.prometheus.Types;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Serializes/deserializes the sink request-entry, the protobuf-generated {@link Types.TimeSeries},
+ * using protobuf.
+ */
+@Internal
+public class PrometheusStateSerializer extends AsyncSinkWriterStateSerializer<Types.TimeSeries> {
+    private static final int VERSION = 1;
+
+    // Copied from AsyncSinkWriterStateSerializer.DATA_IDENTIFIER
+    private static final long DATA_IDENTIFIER = -1;
+
+    @Override
+    protected void serializeRequestToStream(Types.TimeSeries request, DataOutputStream out)
+            throws IOException {
+        byte[] serializedRequest = request.toByteArray();
+        out.write(serializedRequest);
+    }
+
+    @Override
+    protected Types.TimeSeries deserializeRequestFromStream(long requestSize, DataInputStream in)
+            throws IOException {
+        // The size written into the serialized stat is the size of the protobuf-serialized
+        // time-series
+        byte[] requestData = new byte[(int) requestSize];
+        in.read(requestData);
+        return Types.TimeSeries.parseFrom(requestData);
+    }
+
+    @Override
+    public int getVersion() {
+        return VERSION;
+    }
+
+    /**
+     * Overrides the original implementation that assumes the serialized size is the value returned
+     * by {@link PrometheusSinkWriter#getSizeInBytes(Types.TimeSeries)}
+     *
+     * <p>Most of the code is copied from the original implementation of
+     * AsyncSinkWriterStateSerializer.serialize().
+     *
+     * <p>The state is serialized in form of
+     * [DATA_IDENTIFIER,NUM_OF_ELEMENTS,SIZE1,REQUEST1,SIZE2,REQUEST2....], where REQUEST{n} is the
+     * Protobuf-serialized representation of a {@link Types.TimeSeries TimeSeries}. In this
+     * implementation SIZE{n} is the size of the Protobuf serialization, in bytes, that does not
+     * match the "size" of a {@link RequestEntryWrapper}.
+     *
+     * @param bufferedRequestState The buffered request state to be serialized
+     * @return serialized buffered request state
+     * @throws IOException
+     */
+    @Override
+    public byte[] serialize(BufferedRequestState<Types.TimeSeries> bufferedRequestState)
+            throws IOException {
+        Collection<RequestEntryWrapper<Types.TimeSeries>> bufferState =
+                bufferedRequestState.getBufferedRequestEntries();
+
+        try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                final DataOutputStream out = new DataOutputStream(baos)) {
+
+            out.writeLong(DATA_IDENTIFIER); // DATA_IDENTIFIER
+            out.writeInt(bufferState.size()); // NUM_OF_ELEMENTS
+
+            for (RequestEntryWrapper<Types.TimeSeries> wrapper : bufferState) {
+                // In the serialized state we write the size of the serialized representation,
+                // rather than the size
+                // held in RequestEntryWrapper, that is the output of
+                // AsyncSinkWriter.getSizeInBytes()
+                long requestEntrySize =
+                        RequestEntrySizeUtils.requestSerializedSize(wrapper.getRequestEntry());
+                out.writeLong(requestEntrySize); // SIZE{n}
+                serializeRequestToStream(wrapper.getRequestEntry(), out); // REQUEST{n}
+            }
+
+            return baos.toByteArray();
+        }
+    }
+
+    /**
+     * Overrides the original implementation that assumes the serialized size is the value returned
+     * by {@link PrometheusSinkWriter#getSizeInBytes(Types.TimeSeries)}
+     *
+     * <p>See {@link PrometheusStateSerializer#serialize(BufferedRequestState)} for more details.
+     *
+     * @param version The version in which the data was serialized
+     * @param serialized The serialized data
+     * @return a buffered request state wrapping the deserialized time-series.
+     * @throws IOException
+     */
+    @Override
+    public BufferedRequestState<Types.TimeSeries> deserialize(int version, byte[] serialized)
+            throws IOException {
+        try (final ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
+                final DataInputStream in = new DataInputStream(bais)) {
+
+            validateIdentifier(in); // DATA_IDENTIFIER
+            int numberOfElements = in.readInt(); // NUM_OF_ELEMENTS
+
+            List<RequestEntryWrapper<Types.TimeSeries>> serializedState = new ArrayList<>();
+            for (int i = 0; i < numberOfElements; i++) {
+                // This is the size of a request-entry in the serialized state
+                long requestSerializedSize = in.readLong(); // SIZE (the serialized size)
+                Types.TimeSeries requestEntry =
+                        deserializeRequestFromStream(requestSerializedSize, in);
+
+                // The "size" of RequestEntryWrapper must be the size returned by
+                // PrometheusSinkWriter.getSizeInBytes(),
+                // used for batching
+                long requestEntrySize = RequestEntrySizeUtils.requestSizeForBatching(requestEntry);
+                serializedState.add(new RequestEntryWrapper<>(requestEntry, requestEntrySize));
+            }
+
+            return new BufferedRequestState<>(serializedState);
+        }
+    }
+
+    // Copy of the private implementation of AsyncSinkWriterStateSerializer.validateIdentifier()
+    private void validateIdentifier(DataInputStream in) throws IOException {
+        if (in.readLong() != DATA_IDENTIFIER) {
+            throw new IllegalStateException("Corrupted data to deserialize");
+        }
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusTimeSeries.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusTimeSeries.java
new file mode 100644
index 0000000..80fa6ac
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusTimeSeries.java
@@ -0,0 +1,203 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.PublicEvolving;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Pojo used as sink input, containing a single TimeSeries: a list of Labels and a list of Samples.
+ *
+ * <p>metricName is mapped in Prometheus to the value of the mandatory label named '__name__'
+ * labels. The other labels, as key/value, are appended after the '__name__' label.
+ */
+@PublicEvolving
+public class PrometheusTimeSeries implements Serializable {
+    /** A single Label. */
+    public static class Label implements Serializable {
+        private final String name;
+        private final String value;
+
+        public Label(String name, String value) {
+            this.name = name;
+            this.value = value;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public String getValue() {
+            return value;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            Label label = (Label) o;
+            return new EqualsBuilder()
+                    .append(name, label.name)
+                    .append(value, label.value)
+                    .isEquals();
+        }
+
+        @Override
+        public int hashCode() {
+            return new HashCodeBuilder(17, 37).append(name).append(value).toHashCode();
+        }
+    }
+
+    /** A single Sample. */
+    public static class Sample implements Serializable {
+        private final double value;
+        private final long timestamp;
+
+        public Sample(double value, long timestamp) {
+            this.value = value;
+            this.timestamp = timestamp;
+        }
+
+        public double getValue() {
+            return value;
+        }
+
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            Sample sample = (Sample) o;
+            return Double.compare(value, sample.value) == 0 && timestamp == sample.timestamp;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(value, timestamp);
+        }
+    }
+
+    private final Label[] labels;
+    private final Sample[] samples;
+    private final String metricName;
+
+    public PrometheusTimeSeries(String metricName, Label[] labels, Sample[] samples) {
+        this.metricName = metricName;
+        this.labels = labels;
+        this.samples = samples;
+    }
+
+    public Label[] getLabels() {
+        return labels;
+    }
+
+    public Sample[] getSamples() {
+        return samples;
+    }
+
+    public String getMetricName() {
+        return metricName;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static Builder builderFrom(PrometheusTimeSeries other) {
+        return new Builder(
+                Arrays.asList(other.labels), Arrays.asList(other.samples), other.metricName);
+    }
+
+    /** Builder for sink input pojo instance. */
+    public static final class Builder {
+        private List<Label> labels = new ArrayList<>();
+        private List<Sample> samples = new ArrayList<>();
+        private String metricName;
+
+        private Builder(List<Label> labels, List<Sample> samples, String metricName) {
+            this.labels = labels;
+            this.samples = samples;
+            this.metricName = metricName;
+        }
+
+        private Builder() {}
+
+        public Builder withMetricName(String metricName) {
+            this.metricName = metricName;
+            return this;
+        }
+
+        public Builder addLabel(String labelName, String labelValue) {
+            labels.add(new Label(labelName, labelValue));
+            return this;
+        }
+
+        public Builder addSample(double value, long timestamp) {
+            samples.add(new Sample(value, timestamp));
+            return this;
+        }
+
+        public PrometheusTimeSeries build() {
+            return new PrometheusTimeSeries(
+                    metricName,
+                    labels.toArray(new Label[labels.size()]),
+                    samples.toArray(new Sample[samples.size()]));
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        PrometheusTimeSeries that = (PrometheusTimeSeries) o;
+        return Arrays.equals(labels, that.labels)
+                && Arrays.equals(samples, that.samples)
+                && Objects.equals(metricName, that.metricName);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hash(metricName);
+        result = 31 * result + Arrays.hashCode(labels);
+        result = 31 * result + Arrays.hashCode(samples);
+        return result;
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusTimeSeriesConverter.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusTimeSeriesConverter.java
new file mode 100644
index 0000000..8ca7303
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusTimeSeriesConverter.java
@@ -0,0 +1,63 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.PublicEvolving;
+import org.apache.flink.api.connector.sink2.SinkWriter;
+import org.apache.flink.connector.base.sink.writer.ElementConverter;
+import org.apache.flink.connector.prometheus.sink.prometheus.Types;
+
+/**
+ * Converts the sink input {@link PrometheusTimeSeries} into the Protobuf {@link Types.TimeSeries}
+ * that are sent to Prometheus.
+ */
+@PublicEvolving
+public class PrometheusTimeSeriesConverter
+        implements ElementConverter<PrometheusTimeSeries, Types.TimeSeries> {
+
+    private static final String METRIC_NAME_LABEL_NAME = "__name__";
+
+    @Override
+    public Types.TimeSeries apply(PrometheusTimeSeries element, SinkWriter.Context context) {
+        Types.TimeSeries.Builder builder =
+                Types.TimeSeries.newBuilder()
+                        .addLabels(
+                                Types.Label.newBuilder()
+                                        .setName(METRIC_NAME_LABEL_NAME)
+                                        .setValue(element.getMetricName())
+                                        .build());
+
+        for (PrometheusTimeSeries.Label label : element.getLabels()) {
+            builder.addLabels(
+                    Types.Label.newBuilder()
+                            .setName(label.getName())
+                            .setValue(label.getValue())
+                            .build());
+        }
+
+        for (PrometheusTimeSeries.Sample sample : element.getSamples()) {
+            builder.addSamples(
+                    Types.Sample.newBuilder()
+                            .setValue(sample.getValue())
+                            .setTimestamp(sample.getTimestamp())
+                            .build());
+        }
+
+        return builder.build();
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusTimeSeriesLabelsAndMetricNameKeySelector.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusTimeSeriesLabelsAndMetricNameKeySelector.java
new file mode 100644
index 0000000..67fc0c1
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/PrometheusTimeSeriesLabelsAndMetricNameKeySelector.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.PublicEvolving;
+import org.apache.flink.api.java.functions.KeySelector;
+
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+/**
+ * Implementation of KeySelector ensuring the {@link PrometheusTimeSeries} with the same set of
+ * labels end up in the same partition.
+ *
+ * <p>For using the sink with parallelism > 1, the input of the sink must be a keyedStream using
+ * this KeySelector to extract the key. This guarantees TimeSeries with the same set of labels are
+ * written to Prometheus in the same order they are sent to the sink.
+ *
+ * <p>The partition key is the hash of all labels AND the metricName. The metricName is added as
+ * additional label by the sink, before writing to Prometheus.
+ */
+@PublicEvolving
+public class PrometheusTimeSeriesLabelsAndMetricNameKeySelector
+        implements KeySelector<PrometheusTimeSeries, Integer> {
+    @Override
+    public Integer getKey(PrometheusTimeSeries timeSeries) throws Exception {
+        return new HashCodeBuilder(17, 37)
+                .append(timeSeries.getLabels())
+                .append(timeSeries.getMetricName())
+                .build();
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/RequestEntrySizeUtils.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/RequestEntrySizeUtils.java
new file mode 100644
index 0000000..8b8d4e8
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/RequestEntrySizeUtils.java
@@ -0,0 +1,65 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink;
+
+import org.apache.flink.annotation.Internal;
+import org.apache.flink.connector.prometheus.sink.prometheus.Types;
+
+import java.util.Collection;
+
+/** Collection of methods to calculate the sink RequestEntry "size". */
+@Internal
+public class RequestEntrySizeUtils {
+
+    /**
+     * Size of a request entry (a {@link Types.TimeSeries time-series}) for the purpose of batching.
+     * Count the number of {@link Types.Sample samples}
+     *
+     * @param requestEntry a time-series
+     * @return number of Samples in the TimeSeries
+     */
+    public static long requestSizeForBatching(Types.TimeSeries requestEntry) {
+        return requestEntry.getSamplesCount();
+    }
+
+    /**
+     * Serialized size of a request entry {@link Types.TimeSeries TimeSeries}: the number of bytes
+     * of the protobuf- serialized representation of the TimeSeries.
+     *
+     * @param requestEntry a time-series
+     * @return number of bytes
+     */
+    public static long requestSerializedSize(Types.TimeSeries requestEntry) {
+        return requestEntry.getSerializedSize();
+    }
+
+    /**
+     * Count the number of {@link Types.Sample samples} in a collection of {@link Types.TimeSeries
+     * time-series} (a batch).
+     *
+     * @param requestEntries collection of time-series
+     * @return number of samples
+     */
+    public static long countSamples(Collection<Types.TimeSeries> requestEntries) {
+        long count = 0;
+        for (Types.TimeSeries requestEntry : requestEntries) {
+            count += requestSizeForBatching(requestEntry);
+        }
+        return count;
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/errorhandling/PrometheusSinkWriteException.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/errorhandling/PrometheusSinkWriteException.java
new file mode 100644
index 0000000..ef856bc
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/errorhandling/PrometheusSinkWriteException.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.errorhandling;
+
+import org.apache.flink.annotation.Internal;
+
+/** Exception during writing to Prometheus Remote-Write endpoint. */
+@Internal
+public class PrometheusSinkWriteException extends RuntimeException {
+
+    public PrometheusSinkWriteException(String reason) {
+        super("Reason: " + reason);
+    }
+
+    public PrometheusSinkWriteException(String reason, Exception cause) {
+        super("Reason: " + reason, cause);
+    }
+
+    public PrometheusSinkWriteException(
+            String reason,
+            int httpStatusCode,
+            String httpReasonPhrase,
+            int timeSeriesCount,
+            long sampleCount,
+            String httpResponseBody) {
+        super(
+                String.format(
+                        "Reason: %s. Http response: %d,%s (%s) .The offending write-request contains %d time-series and %d samples",
+                        reason,
+                        httpStatusCode,
+                        httpReasonPhrase,
+                        httpResponseBody,
+                        timeSeriesCount,
+                        sampleCount));
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/http/PrometheusAsyncHttpClientBuilder.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/http/PrometheusAsyncHttpClientBuilder.java
new file mode 100644
index 0000000..1a4ec3e
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/http/PrometheusAsyncHttpClientBuilder.java
@@ -0,0 +1,97 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.http;
+
+import org.apache.flink.annotation.PublicEvolving;
+import org.apache.flink.connector.prometheus.sink.PrometheusSinkConfiguration;
+import org.apache.flink.connector.prometheus.sink.metrics.SinkMetricsCallback;
+import org.apache.flink.util.Preconditions;
+
+import org.apache.hc.client5.http.config.TlsConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.reactor.IOReactorConfig;
+import org.apache.hc.core5.util.Timeout;
+
+import java.io.Serializable;
+import java.util.Optional;
+
+/**
+ * Builder for async http client that will retry, based on the {@link RemoteWriteRetryStrategy}
+ * specified.
+ */
+@PublicEvolving
+public class PrometheusAsyncHttpClientBuilder implements Serializable {
+    public static final int DEFAULT_SOCKET_TIMEOUT_MS = 5000;
+
+    private final PrometheusSinkConfiguration.RetryConfiguration retryConfiguration;
+    private Integer socketTimeoutMs;
+
+    public PrometheusAsyncHttpClientBuilder(
+            PrometheusSinkConfiguration.RetryConfiguration retryConfiguration) {
+        this.retryConfiguration = retryConfiguration;
+    }
+
+    public PrometheusAsyncHttpClientBuilder setSocketTimeout(int socketTimeoutMs) {
+        this.socketTimeoutMs = socketTimeoutMs;
+        return this;
+    }
+
+    public CloseableHttpAsyncClient buildAndStartClient(SinkMetricsCallback metricsCallback) {
+        int actualSocketTimeoutMs =
+                Optional.ofNullable(socketTimeoutMs).orElse(DEFAULT_SOCKET_TIMEOUT_MS);
+
+        Preconditions.checkArgument(
+                retryConfiguration.getInitialRetryDelayMS() >= 0,
+                "Initial retry delay must be >= 0");
+        Preconditions.checkArgument(
+                retryConfiguration.getMaxRetryDelayMS()
+                        >= retryConfiguration.getInitialRetryDelayMS(),
+                "Max retry delay must be >= initial retry delay");
+        Preconditions.checkArgument(
+                retryConfiguration.getMaxRetryCount() >= 0, "Max retry count must be >= 0");
+        Preconditions.checkArgument(actualSocketTimeoutMs >= 0, "Socket timeout must be >= 0");
+
+        final IOReactorConfig ioReactorConfig =
+                IOReactorConfig.custom()
+                        // Remote-Writes must be single-threaded to prevent out-of-order writes
+                        .setIoThreadCount(1)
+                        .setSoTimeout(Timeout.ofMilliseconds(actualSocketTimeoutMs))
+                        .build();
+
+        TlsConfig tlsConfig =
+                TlsConfig.custom().setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1).build();
+        PoolingAsyncClientConnectionManager connectionManager =
+                PoolingAsyncClientConnectionManagerBuilder.create()
+                        .setDefaultTlsConfig(tlsConfig)
+                        .build();
+        CloseableHttpAsyncClient client =
+                HttpAsyncClients.custom()
+                        .setConnectionManager(connectionManager)
+                        .setIOReactorConfig(ioReactorConfig)
+                        .setRetryStrategy(
+                                new RemoteWriteRetryStrategy(retryConfiguration, metricsCallback))
+                        .build();
+
+        client.start();
+        return client;
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/http/RemoteWriteResponseClassifier.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/http/RemoteWriteResponseClassifier.java
new file mode 100644
index 0000000..2cc038a
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/http/RemoteWriteResponseClassifier.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.http;
+
+import org.apache.flink.annotation.Internal;
+
+import org.apache.hc.core5.http.HttpResponse;
+
+/** Classify http responses based on the status code. */
+@Internal
+public class RemoteWriteResponseClassifier {
+    public static boolean isRetriableErrorResponse(HttpResponse response) {
+        int statusCode = response.getCode();
+        return statusCode >= 500 | statusCode == 429;
+    }
+
+    public static boolean isNonRetriableErrorResponse(HttpResponse response) {
+        int statusCode = response.getCode();
+        return (statusCode >= 400 && statusCode < 500) && statusCode != 429;
+    }
+
+    public static boolean isSuccessResponse(HttpResponse response) {
+        int statusCode = response.getCode();
+        return (statusCode >= 200 && statusCode < 300);
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/http/RemoteWriteRetryStrategy.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/http/RemoteWriteRetryStrategy.java
new file mode 100644
index 0000000..1b8fdc5
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/http/RemoteWriteRetryStrategy.java
@@ -0,0 +1,131 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.http;
+
+import org.apache.flink.annotation.Internal;
+import org.apache.flink.connector.prometheus.sink.PrometheusSinkConfiguration;
+import org.apache.flink.connector.prometheus.sink.metrics.SinkMetricsCallback;
+
+import org.apache.hc.client5.http.HttpRequestRetryStrategy;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.TimeValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLException;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.ConnectException;
+import java.net.NoRouteToHostException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Retry strategy for the http client.
+ *
+ * <p>Based on the http status code returned or the exception thrown, this strategy either retries
+ * with an exponential backoff strategy or immediately fail.
+ *
+ * <p>Response status codes are classified as retriable or non-retriable using {@link
+ * RemoteWriteResponseClassifier}.
+ *
+ * <p>All {@link IOException} are considered retriable, except for {@link InterruptedIOException},
+ * {@link UnknownHostException}, {@link ConnectException}, {@link NoRouteToHostException}, and
+ * {@link SSLException}.
+ */
+@Internal
+public class RemoteWriteRetryStrategy implements HttpRequestRetryStrategy {
+    private static final Logger LOG = LoggerFactory.getLogger(RemoteWriteRetryStrategy.class);
+
+    /** List of exceptions considered non-recoverable (non-retriable). */
+    private static final List<Class<? extends IOException>> NON_RECOVERABLE_EXCEPTIONS =
+            Collections.unmodifiableList(
+                    new ArrayList<Class<? extends IOException>>() {
+                        {
+                            add(InterruptedIOException.class);
+                            add(UnknownHostException.class);
+                            add(ConnectException.class);
+                            add(NoRouteToHostException.class);
+                            add(SSLException.class);
+                        }
+                    });
+
+    private final long initialRetryDelayMs;
+    private final long maxRetryDelayMs;
+    private final int maxRetryCount;
+
+    private final SinkMetricsCallback metricsCallback;
+
+    public RemoteWriteRetryStrategy(
+            PrometheusSinkConfiguration.RetryConfiguration retryConfiguration,
+            SinkMetricsCallback metricsCallback) {
+        this.initialRetryDelayMs = retryConfiguration.getInitialRetryDelayMS();
+        this.maxRetryDelayMs = retryConfiguration.getMaxRetryDelayMS();
+        this.maxRetryCount = retryConfiguration.getMaxRetryCount();
+        this.metricsCallback = metricsCallback;
+    }
+
+    @Override
+    public boolean retryRequest(
+            HttpRequest httpRequest, IOException e, int execCount, HttpContext httpContext) {
+        // Retry on any IOException except those considered non-recoverable
+        boolean retry =
+                (execCount <= maxRetryCount)
+                        && !(NON_RECOVERABLE_EXCEPTIONS.contains(e.getClass()));
+        LOG.debug(
+                "{} retry on {}, at execution {}",
+                (retry) ? "DO" : "DO NOT",
+                e.getClass(),
+                execCount);
+        countRetry(retry);
+        return retry;
+    }
+
+    @Override
+    public boolean retryRequest(HttpResponse httpResponse, int execCount, HttpContext httpContext) {
+        boolean retry =
+                (execCount <= maxRetryCount)
+                        && RemoteWriteResponseClassifier.isRetriableErrorResponse(httpResponse);
+        LOG.debug(
+                "{} retry on response {} {}, at execution {}",
+                (retry) ? "DO" : "DO NOT",
+                httpResponse.getCode(),
+                httpResponse.getReasonPhrase(),
+                execCount);
+        countRetry(retry);
+        return retry;
+    }
+
+    @Override
+    public TimeValue getRetryInterval(
+            HttpResponse httpResponse, int execCount, HttpContext httpContext) {
+        long calculatedDelay = initialRetryDelayMs << (execCount - 1);
+        return TimeValue.ofMilliseconds(Math.min(calculatedDelay, maxRetryDelayMs));
+    }
+
+    private void countRetry(boolean retry) {
+        if (retry) {
+            metricsCallback.onWriteRequestRetry();
+        }
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/metrics/SinkMetrics.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/metrics/SinkMetrics.java
new file mode 100644
index 0000000..84c6205
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/metrics/SinkMetrics.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.metrics;
+
+import org.apache.flink.annotation.Internal;
+import org.apache.flink.metrics.Counter;
+import org.apache.flink.metrics.MetricGroup;
+
+/** Wraps all metrics in a single class. */
+@Internal
+public class SinkMetrics {
+    private final Counter[] counters;
+
+    private SinkMetrics(Counter[] counters) {
+        this.counters = counters;
+    }
+
+    public void inc(SinkCounter counter, long value) {
+        counters[counter.ordinal()].inc(value);
+    }
+
+    public void inc(SinkCounter counter) {
+        counters[counter.ordinal()].inc();
+    }
+
+    /** Register all custom sink metrics and return an of this wrapper class. */
+    public static SinkMetrics registerSinkMetrics(MetricGroup metrics) {
+        // Register all counters
+        Counter[] counters = new Counter[SinkCounter.values().length];
+        for (SinkCounter metric : SinkCounter.values()) {
+            counters[metric.ordinal()] = metrics.counter(metric.getMetricName());
+        }
+        return new SinkMetrics(counters);
+    }
+
+    /** Enum defining all sink counters. */
+    public enum SinkCounter {
+
+        /** Total number of Samples that were dropped because of causing non-retriable errors. */
+        NUM_SAMPLES_NON_RETRIABLE_DROPPED("numSamplesNonRetriableDropped"),
+
+        /** Number of Samples dropped after reaching retry limit on retriable errors. */
+        NUM_SAMPLES_RETRY_LIMIT_DROPPED("numSamplesRetryLimitDropped"),
+
+        /**
+         * Total number of Samples dropped due to any reasons: retriable errors reaching retry
+         * limit, non-retriable errors, unexpected IO errors.
+         */
+        NUM_SAMPLES_DROPPED("numSamplesDropped"),
+
+        /** Number of Samples successfully written to Prometheus. */
+        NUM_SAMPLES_OUT("numSamplesOut"),
+
+        /** Number of WriteRequests successfully sent to Prometheus. */
+        NUM_WRITE_REQUESTS_OUT("numWriteRequestsOut"),
+
+        /** Number of permanently failed WriteRequests. */
+        NUM_WRITE_REQUESTS_PERMANENTLY_FAILED("numWriteRequestsPermanentlyFailed"),
+
+        /** Number of WriteRequests retries. */
+        NUM_WRITE_REQUESTS_RETRIES("numWriteRequestsRetries");
+
+        private final String metricName;
+
+        public String getMetricName() {
+            return metricName;
+        }
+
+        SinkCounter(String metricName) {
+            this.metricName = metricName;
+        }
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/metrics/SinkMetricsCallback.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/metrics/SinkMetricsCallback.java
new file mode 100644
index 0000000..49c36d5
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/metrics/SinkMetricsCallback.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.connector.prometheus.sink.metrics;
+
+import org.apache.flink.annotation.Internal;
+
+import static org.apache.flink.connector.prometheus.sink.metrics.SinkMetrics.SinkCounter.NUM_SAMPLES_DROPPED;
+import static org.apache.flink.connector.prometheus.sink.metrics.SinkMetrics.SinkCounter.NUM_SAMPLES_NON_RETRIABLE_DROPPED;
+import static org.apache.flink.connector.prometheus.sink.metrics.SinkMetrics.SinkCounter.NUM_SAMPLES_OUT;
+import static org.apache.flink.connector.prometheus.sink.metrics.SinkMetrics.SinkCounter.NUM_SAMPLES_RETRY_LIMIT_DROPPED;
+import static org.apache.flink.connector.prometheus.sink.metrics.SinkMetrics.SinkCounter.NUM_WRITE_REQUESTS_OUT;
+import static org.apache.flink.connector.prometheus.sink.metrics.SinkMetrics.SinkCounter.NUM_WRITE_REQUESTS_PERMANENTLY_FAILED;
+
+/** Callback updating {@link SinkMetrics} on specific request outcomes. */
+@Internal
+public class SinkMetricsCallback {
+
+    private final SinkMetrics metrics;
+
+    /**
+     * Instantiate a callback passing the collection of metrics to mutate.
+     *
+     * @param metrics collection of metrics that will be mutated
+     */
+    public SinkMetricsCallback(SinkMetrics metrics) {
+        this.metrics = metrics;
+    }
+
+    private void onFailedWriteRequest(long sampleCount) {
+        metrics.inc(NUM_SAMPLES_DROPPED, sampleCount);
+        metrics.inc(NUM_WRITE_REQUESTS_PERMANENTLY_FAILED);
+    }
+
+    public void onSuccessfulWriteRequest(long sampleCount) {
+        metrics.inc(NUM_SAMPLES_OUT, sampleCount);
+        metrics.inc(NUM_WRITE_REQUESTS_OUT);
+    }
+
+    public void onFailedWriteRequestForNonRetriableError(long sampleCount) {
+        metrics.inc(NUM_SAMPLES_NON_RETRIABLE_DROPPED, sampleCount);
+        onFailedWriteRequest(sampleCount);
+    }
+
+    public void onFailedWriteRequestForRetryLimitExceeded(long sampleCount) {
+        metrics.inc(NUM_SAMPLES_RETRY_LIMIT_DROPPED, sampleCount);
+        onFailedWriteRequest(sampleCount);
+    }
+
+    public void onFailedWriteRequestForHttpClientIoFail(long sampleCount) {
+        onFailedWriteRequest(sampleCount);
+    }
+
+    public void onWriteRequestRetry() {
+        metrics.inc(SinkMetrics.SinkCounter.NUM_WRITE_REQUESTS_RETRIES);
+    }
+}
diff --git a/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/prometheus/Remote.java b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/prometheus/Remote.java
new file mode 100644
index 0000000..17e66f5
--- /dev/null
+++ b/flink-connector-prometheus/src/main/java/org/apache/flink/connector/prometheus/sink/prometheus/Remote.java
@@ -0,0 +1,6661 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: remote.proto
+
+package org.apache.flink.connector.prometheus.sink.prometheus;
+
+public final class Remote {
+    private Remote() {}
+
+    public static void registerAllExtensions(com.google.protobuf.ExtensionRegistryLite registry) {}
+
+    public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry) {
+        registerAllExtensions((com.google.protobuf.ExtensionRegistryLite) registry);
+    }
+
+    public interface WriteRequestOrBuilder
+            extends
+            // @@protoc_insertion_point(interface_extends:prometheus.WriteRequest)
+            com.google.protobuf.MessageOrBuilder {
+
+        /**
+         * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        java.util.List<Types.TimeSeries> getTimeseriesList();
+
+        /**
+         * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        Types.TimeSeries getTimeseries(int index);
+
+        /**
+         * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        int getTimeseriesCount();
+
+        /**
+         * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        java.util.List<? extends Types.TimeSeriesOrBuilder> getTimeseriesOrBuilderList();
+
+        /**
+         * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        Types.TimeSeriesOrBuilder getTimeseriesOrBuilder(int index);
+
+        /**
+         * <code>repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        java.util.List<Types.MetricMetadata> getMetadataList();
+
+        /**
+         * <code>repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        Types.MetricMetadata getMetadata(int index);
+
+        /**
+         * <code>repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        int getMetadataCount();
+
+        /**
+         * <code>repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        java.util.List<? extends Types.MetricMetadataOrBuilder> getMetadataOrBuilderList();
+
+        /**
+         * <code>repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        Types.MetricMetadataOrBuilder getMetadataOrBuilder(int index);
+    }
+
+    /** Protobuf type {@code prometheus.WriteRequest} */
+    public static final class WriteRequest extends com.google.protobuf.GeneratedMessageV3
+            implements
+            // @@protoc_insertion_point(message_implements:prometheus.WriteRequest)
+            WriteRequestOrBuilder {
+        private static final long serialVersionUID = 0L;
+
+        // Use WriteRequest.newBuilder() to construct.
+        private WriteRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+            super(builder);
+        }
+
+        private WriteRequest() {
+            timeseries_ = java.util.Collections.emptyList();
+            metadata_ = java.util.Collections.emptyList();
+        }
+
+        @Override
+        @SuppressWarnings({"unused"})
+        protected Object newInstance(UnusedPrivateParameter unused) {
+            return new WriteRequest();
+        }
+
+        public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
+            return internal_static_prometheus_WriteRequest_descriptor;
+        }
+
+        @Override
+        protected FieldAccessorTable internalGetFieldAccessorTable() {
+            return internal_static_prometheus_WriteRequest_fieldAccessorTable
+                    .ensureFieldAccessorsInitialized(WriteRequest.class, Builder.class);
+        }
+
+        public static final int TIMESERIES_FIELD_NUMBER = 1;
+
+        @SuppressWarnings("serial")
+        private java.util.List<Types.TimeSeries> timeseries_;
+
+        /**
+         * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        @Override
+        public java.util.List<Types.TimeSeries> getTimeseriesList() {
+            return timeseries_;
+        }
+
+        /**
+         * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        @Override
+        public java.util.List<? extends Types.TimeSeriesOrBuilder> getTimeseriesOrBuilderList() {
+            return timeseries_;
+        }
+
+        /**
+         * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        @Override
+        public int getTimeseriesCount() {
+            return timeseries_.size();
+        }
+
+        /**
+         * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        @Override
+        public Types.TimeSeries getTimeseries(int index) {
+            return timeseries_.get(index);
+        }
+
+        /**
+         * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        @Override
+        public Types.TimeSeriesOrBuilder getTimeseriesOrBuilder(int index) {
+            return timeseries_.get(index);
+        }
+
+        public static final int METADATA_FIELD_NUMBER = 3;
+
+        @SuppressWarnings("serial")
+        private java.util.List<Types.MetricMetadata> metadata_;
+
+        /**
+         * <code>repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        @Override
+        public java.util.List<Types.MetricMetadata> getMetadataList() {
+            return metadata_;
+        }
+
+        /**
+         * <code>repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        @Override
+        public java.util.List<? extends Types.MetricMetadataOrBuilder> getMetadataOrBuilderList() {
+            return metadata_;
+        }
+
+        /**
+         * <code>repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        @Override
+        public int getMetadataCount() {
+            return metadata_.size();
+        }
+
+        /**
+         * <code>repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        @Override
+        public Types.MetricMetadata getMetadata(int index) {
+            return metadata_.get(index);
+        }
+
+        /**
+         * <code>repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+         * </code>
+         */
+        @Override
+        public Types.MetricMetadataOrBuilder getMetadataOrBuilder(int index) {
+            return metadata_.get(index);
+        }
+
+        private byte memoizedIsInitialized = -1;
+
+        @Override
+        public final boolean isInitialized() {
+            byte isInitialized = memoizedIsInitialized;
+            if (isInitialized == 1) return true;
+            if (isInitialized == 0) return false;
+
+            memoizedIsInitialized = 1;
+            return true;
+        }
+
+        @Override
+        public void writeTo(com.google.protobuf.CodedOutputStream output)
+                throws java.io.IOException {
+            for (int i = 0; i < timeseries_.size(); i++) {
+                output.writeMessage(1, timeseries_.get(i));
+            }
+            for (int i = 0; i < metadata_.size(); i++) {
+                output.writeMessage(3, metadata_.get(i));
+            }
+            getUnknownFields().writeTo(output);
+        }
+
+        @Override
+        public int getSerializedSize() {
+            int size = memoizedSize;
+            if (size != -1) return size;
+
+            size = 0;
+            for (int i = 0; i < timeseries_.size(); i++) {
+                size +=
+                        com.google.protobuf.CodedOutputStream.computeMessageSize(
+                                1, timeseries_.get(i));
+            }
+            for (int i = 0; i < metadata_.size(); i++) {
+                size +=
+                        com.google.protobuf.CodedOutputStream.computeMessageSize(
+                                3, metadata_.get(i));
+            }
+            size += getUnknownFields().getSerializedSize();
+            memoizedSize = size;
+            return size;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof WriteRequest)) {
+                return super.equals(obj);
+            }
+            WriteRequest other = (WriteRequest) obj;
+
+            if (!getTimeseriesList().equals(other.getTimeseriesList())) return false;
+            if (!getMetadataList().equals(other.getMetadataList())) return false;
+            if (!getUnknownFields().equals(other.getUnknownFields())) return false;
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            if (memoizedHashCode != 0) {
+                return memoizedHashCode;
+            }
+            int hash = 41;
+            hash = (19 * hash) + getDescriptor().hashCode();
+            if (getTimeseriesCount() > 0) {
+                hash = (37 * hash) + TIMESERIES_FIELD_NUMBER;
+                hash = (53 * hash) + getTimeseriesList().hashCode();
+            }
+            if (getMetadataCount() > 0) {
+                hash = (37 * hash) + METADATA_FIELD_NUMBER;
+                hash = (53 * hash) + getMetadataList().hashCode();
+            }
+            hash = (29 * hash) + getUnknownFields().hashCode();
+            memoizedHashCode = hash;
+            return hash;
+        }
+
+        public static WriteRequest parseFrom(java.nio.ByteBuffer data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static WriteRequest parseFrom(
+                java.nio.ByteBuffer data,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static WriteRequest parseFrom(com.google.protobuf.ByteString data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static WriteRequest parseFrom(
+                com.google.protobuf.ByteString data,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static WriteRequest parseFrom(byte[] data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static WriteRequest parseFrom(
+                byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static WriteRequest parseFrom(java.io.InputStream input) throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
+        }
+
+        public static WriteRequest parseFrom(
+                java.io.InputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        public static WriteRequest parseDelimitedFrom(java.io.InputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(
+                    PARSER, input);
+        }
+
+        public static WriteRequest parseDelimitedFrom(
+                java.io.InputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        public static WriteRequest parseFrom(com.google.protobuf.CodedInputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
+        }
+
+        public static WriteRequest parseFrom(
+                com.google.protobuf.CodedInputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        @Override
+        public Builder newBuilderForType() {
+            return newBuilder();
+        }
+
+        public static Builder newBuilder() {
+            return DEFAULT_INSTANCE.toBuilder();
+        }
+
+        public static Builder newBuilder(WriteRequest prototype) {
+            return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+        }
+
+        @Override
+        public Builder toBuilder() {
+            return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this);
+        }
+
+        @Override
+        protected Builder newBuilderForType(BuilderParent parent) {
+            Builder builder = new Builder(parent);
+            return builder;
+        }
+
+        /** Protobuf type {@code prometheus.WriteRequest} */
+        public static final class Builder
+                extends com.google.protobuf.GeneratedMessageV3.Builder<Builder>
+                implements
+                // @@protoc_insertion_point(builder_implements:prometheus.WriteRequest)
+                WriteRequestOrBuilder {
+            public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
+                return internal_static_prometheus_WriteRequest_descriptor;
+            }
+
+            @Override
+            protected FieldAccessorTable internalGetFieldAccessorTable() {
+                return internal_static_prometheus_WriteRequest_fieldAccessorTable
+                        .ensureFieldAccessorsInitialized(WriteRequest.class, Builder.class);
+            }
+
+            // Construct using
+            // org.apache.flink.connector.prometheus.sink.prometheus.Remote.WriteRequest.newBuilder()
+            private Builder() {}
+
+            private Builder(BuilderParent parent) {
+                super(parent);
+            }
+
+            @Override
+            public Builder clear() {
+                super.clear();
+                bitField0_ = 0;
+                if (timeseriesBuilder_ == null) {
+                    timeseries_ = java.util.Collections.emptyList();
+                } else {
+                    timeseries_ = null;
+                    timeseriesBuilder_.clear();
+                }
+                bitField0_ = (bitField0_ & ~0x00000001);
+                if (metadataBuilder_ == null) {
+                    metadata_ = java.util.Collections.emptyList();
+                } else {
+                    metadata_ = null;
+                    metadataBuilder_.clear();
+                }
+                bitField0_ = (bitField0_ & ~0x00000002);
+                return this;
+            }
+
+            @Override
+            public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() {
+                return internal_static_prometheus_WriteRequest_descriptor;
+            }
+
+            @Override
+            public WriteRequest getDefaultInstanceForType() {
+                return getDefaultInstance();
+            }
+
+            @Override
+            public WriteRequest build() {
+                WriteRequest result = buildPartial();
+                if (!result.isInitialized()) {
+                    throw newUninitializedMessageException(result);
+                }
+                return result;
+            }
+
+            @Override
+            public WriteRequest buildPartial() {
+                WriteRequest result = new WriteRequest(this);
+                buildPartialRepeatedFields(result);
+                if (bitField0_ != 0) {
+                    buildPartial0(result);
+                }
+                onBuilt();
+                return result;
+            }
+
+            private void buildPartialRepeatedFields(WriteRequest result) {
+                if (timeseriesBuilder_ == null) {
+                    if (((bitField0_ & 0x00000001) != 0)) {
+                        timeseries_ = java.util.Collections.unmodifiableList(timeseries_);
+                        bitField0_ = (bitField0_ & ~0x00000001);
+                    }
+                    result.timeseries_ = timeseries_;
+                } else {
+                    result.timeseries_ = timeseriesBuilder_.build();
+                }
+                if (metadataBuilder_ == null) {
+                    if (((bitField0_ & 0x00000002) != 0)) {
+                        metadata_ = java.util.Collections.unmodifiableList(metadata_);
+                        bitField0_ = (bitField0_ & ~0x00000002);
+                    }
+                    result.metadata_ = metadata_;
+                } else {
+                    result.metadata_ = metadataBuilder_.build();
+                }
+            }
+
+            private void buildPartial0(WriteRequest result) {
+                int from_bitField0_ = bitField0_;
+            }
+
+            @Override
+            public Builder mergeFrom(com.google.protobuf.Message other) {
+                if (other instanceof WriteRequest) {
+                    return mergeFrom((WriteRequest) other);
+                } else {
+                    super.mergeFrom(other);
+                    return this;
+                }
+            }
+
+            public Builder mergeFrom(WriteRequest other) {
+                if (other == getDefaultInstance()) return this;
+                if (timeseriesBuilder_ == null) {
+                    if (!other.timeseries_.isEmpty()) {
+                        if (timeseries_.isEmpty()) {
+                            timeseries_ = other.timeseries_;
+                            bitField0_ = (bitField0_ & ~0x00000001);
+                        } else {
+                            ensureTimeseriesIsMutable();
+                            timeseries_.addAll(other.timeseries_);
+                        }
+                        onChanged();
+                    }
+                } else {
+                    if (!other.timeseries_.isEmpty()) {
+                        if (timeseriesBuilder_.isEmpty()) {
+                            timeseriesBuilder_.dispose();
+                            timeseriesBuilder_ = null;
+                            timeseries_ = other.timeseries_;
+                            bitField0_ = (bitField0_ & ~0x00000001);
+                            timeseriesBuilder_ =
+                                    com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders
+                                            ? getTimeseriesFieldBuilder()
+                                            : null;
+                        } else {
+                            timeseriesBuilder_.addAllMessages(other.timeseries_);
+                        }
+                    }
+                }
+                if (metadataBuilder_ == null) {
+                    if (!other.metadata_.isEmpty()) {
+                        if (metadata_.isEmpty()) {
+                            metadata_ = other.metadata_;
+                            bitField0_ = (bitField0_ & ~0x00000002);
+                        } else {
+                            ensureMetadataIsMutable();
+                            metadata_.addAll(other.metadata_);
+                        }
+                        onChanged();
+                    }
+                } else {
+                    if (!other.metadata_.isEmpty()) {
+                        if (metadataBuilder_.isEmpty()) {
+                            metadataBuilder_.dispose();
+                            metadataBuilder_ = null;
+                            metadata_ = other.metadata_;
+                            bitField0_ = (bitField0_ & ~0x00000002);
+                            metadataBuilder_ =
+                                    com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders
+                                            ? getMetadataFieldBuilder()
+                                            : null;
+                        } else {
+                            metadataBuilder_.addAllMessages(other.metadata_);
+                        }
+                    }
+                }
+                this.mergeUnknownFields(other.getUnknownFields());
+                onChanged();
+                return this;
+            }
+
+            @Override
+            public final boolean isInitialized() {
+                return true;
+            }
+
+            @Override
+            public Builder mergeFrom(
+                    com.google.protobuf.CodedInputStream input,
+                    com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                    throws java.io.IOException {
+                if (extensionRegistry == null) {
+                    throw new NullPointerException();
+                }
+                try {
+                    boolean done = false;
+                    while (!done) {
+                        int tag = input.readTag();
+                        switch (tag) {
+                            case 0:
+                                done = true;
+                                break;
+                            case 10:
+                                {
+                                    Types.TimeSeries m =
+                                            input.readMessage(
+                                                    Types.TimeSeries.parser(), extensionRegistry);
+                                    if (timeseriesBuilder_ == null) {
+                                        ensureTimeseriesIsMutable();
+                                        timeseries_.add(m);
+                                    } else {
+                                        timeseriesBuilder_.addMessage(m);
+                                    }
+                                    break;
+                                } // case 10
+                            case 26:
+                                {
+                                    Types.MetricMetadata m =
+                                            input.readMessage(
+                                                    Types.MetricMetadata.parser(),
+                                                    extensionRegistry);
+                                    if (metadataBuilder_ == null) {
+                                        ensureMetadataIsMutable();
+                                        metadata_.add(m);
+                                    } else {
+                                        metadataBuilder_.addMessage(m);
+                                    }
+                                    break;
+                                } // case 26
+                            default:
+                                {
+                                    if (!super.parseUnknownField(input, extensionRegistry, tag)) {
+                                        done = true; // was an endgroup tag
+                                    }
+                                    break;
+                                } // default:
+                        } // switch (tag)
+                    } // while (!done)
+                } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+                    throw e.unwrapIOException();
+                } finally {
+                    onChanged();
+                } // finally
+                return this;
+            }
+
+            private int bitField0_;
+
+            private java.util.List<Types.TimeSeries> timeseries_ =
+                    java.util.Collections.emptyList();
+
+            private void ensureTimeseriesIsMutable() {
+                if (!((bitField0_ & 0x00000001) != 0)) {
+                    timeseries_ = new java.util.ArrayList<Types.TimeSeries>(timeseries_);
+                    bitField0_ |= 0x00000001;
+                }
+            }
+
+            private com.google.protobuf.RepeatedFieldBuilderV3<
+                            Types.TimeSeries, Types.TimeSeries.Builder, Types.TimeSeriesOrBuilder>
+                    timeseriesBuilder_;
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public java.util.List<Types.TimeSeries> getTimeseriesList() {
+                if (timeseriesBuilder_ == null) {
+                    return java.util.Collections.unmodifiableList(timeseries_);
+                } else {
+                    return timeseriesBuilder_.getMessageList();
+                }
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public int getTimeseriesCount() {
+                if (timeseriesBuilder_ == null) {
+                    return timeseries_.size();
+                } else {
+                    return timeseriesBuilder_.getCount();
+                }
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Types.TimeSeries getTimeseries(int index) {
+                if (timeseriesBuilder_ == null) {
+                    return timeseries_.get(index);
+                } else {
+                    return timeseriesBuilder_.getMessage(index);
+                }
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder setTimeseries(int index, Types.TimeSeries value) {
+                if (timeseriesBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureTimeseriesIsMutable();
+                    timeseries_.set(index, value);
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.setMessage(index, value);
+                }
+                return this;
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder setTimeseries(int index, Types.TimeSeries.Builder builderForValue) {
+                if (timeseriesBuilder_ == null) {
+                    ensureTimeseriesIsMutable();
+                    timeseries_.set(index, builderForValue.build());
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.setMessage(index, builderForValue.build());
+                }
+                return this;
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder addTimeseries(Types.TimeSeries value) {
+                if (timeseriesBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureTimeseriesIsMutable();
+                    timeseries_.add(value);
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.addMessage(value);
+                }
+                return this;
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder addTimeseries(int index, Types.TimeSeries value) {
+                if (timeseriesBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureTimeseriesIsMutable();
+                    timeseries_.add(index, value);
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.addMessage(index, value);
+                }
+                return this;
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder addTimeseries(Types.TimeSeries.Builder builderForValue) {
+                if (timeseriesBuilder_ == null) {
+                    ensureTimeseriesIsMutable();
+                    timeseries_.add(builderForValue.build());
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.addMessage(builderForValue.build());
+                }
+                return this;
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder addTimeseries(int index, Types.TimeSeries.Builder builderForValue) {
+                if (timeseriesBuilder_ == null) {
+                    ensureTimeseriesIsMutable();
+                    timeseries_.add(index, builderForValue.build());
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.addMessage(index, builderForValue.build());
+                }
+                return this;
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder addAllTimeseries(Iterable<? extends Types.TimeSeries> values) {
+                if (timeseriesBuilder_ == null) {
+                    ensureTimeseriesIsMutable();
+                    com.google.protobuf.AbstractMessageLite.Builder.addAll(values, timeseries_);
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.addAllMessages(values);
+                }
+                return this;
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder clearTimeseries() {
+                if (timeseriesBuilder_ == null) {
+                    timeseries_ = java.util.Collections.emptyList();
+                    bitField0_ = (bitField0_ & ~0x00000001);
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.clear();
+                }
+                return this;
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder removeTimeseries(int index) {
+                if (timeseriesBuilder_ == null) {
+                    ensureTimeseriesIsMutable();
+                    timeseries_.remove(index);
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.remove(index);
+                }
+                return this;
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Types.TimeSeries.Builder getTimeseriesBuilder(int index) {
+                return getTimeseriesFieldBuilder().getBuilder(index);
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Types.TimeSeriesOrBuilder getTimeseriesOrBuilder(int index) {
+                if (timeseriesBuilder_ == null) {
+                    return timeseries_.get(index);
+                } else {
+                    return timeseriesBuilder_.getMessageOrBuilder(index);
+                }
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public java.util.List<? extends Types.TimeSeriesOrBuilder>
+                    getTimeseriesOrBuilderList() {
+                if (timeseriesBuilder_ != null) {
+                    return timeseriesBuilder_.getMessageOrBuilderList();
+                } else {
+                    return java.util.Collections.unmodifiableList(timeseries_);
+                }
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Types.TimeSeries.Builder addTimeseriesBuilder() {
+                return getTimeseriesFieldBuilder()
+                        .addBuilder(Types.TimeSeries.getDefaultInstance());
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Types.TimeSeries.Builder addTimeseriesBuilder(int index) {
+                return getTimeseriesFieldBuilder()
+                        .addBuilder(index, Types.TimeSeries.getDefaultInstance());
+            }
+
+            /**
+             * <code>repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public java.util.List<Types.TimeSeries.Builder> getTimeseriesBuilderList() {
+                return getTimeseriesFieldBuilder().getBuilderList();
+            }
+
+            private com.google.protobuf.RepeatedFieldBuilderV3<
+                            Types.TimeSeries, Types.TimeSeries.Builder, Types.TimeSeriesOrBuilder>
+                    getTimeseriesFieldBuilder() {
+                if (timeseriesBuilder_ == null) {
+                    timeseriesBuilder_ =
+                            new com.google.protobuf.RepeatedFieldBuilderV3<
+                                    Types.TimeSeries,
+                                    Types.TimeSeries.Builder,
+                                    Types.TimeSeriesOrBuilder>(
+                                    timeseries_,
+                                    ((bitField0_ & 0x00000001) != 0),
+                                    getParentForChildren(),
+                                    isClean());
+                    timeseries_ = null;
+                }
+                return timeseriesBuilder_;
+            }
+
+            private java.util.List<Types.MetricMetadata> metadata_ =
+                    java.util.Collections.emptyList();
+
+            private void ensureMetadataIsMutable() {
+                if (!((bitField0_ & 0x00000002) != 0)) {
+                    metadata_ = new java.util.ArrayList<Types.MetricMetadata>(metadata_);
+                    bitField0_ |= 0x00000002;
+                }
+            }
+
+            private com.google.protobuf.RepeatedFieldBuilderV3<
+                            Types.MetricMetadata,
+                            Types.MetricMetadata.Builder,
+                            Types.MetricMetadataOrBuilder>
+                    metadataBuilder_;
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public java.util.List<Types.MetricMetadata> getMetadataList() {
+                if (metadataBuilder_ == null) {
+                    return java.util.Collections.unmodifiableList(metadata_);
+                } else {
+                    return metadataBuilder_.getMessageList();
+                }
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public int getMetadataCount() {
+                if (metadataBuilder_ == null) {
+                    return metadata_.size();
+                } else {
+                    return metadataBuilder_.getCount();
+                }
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Types.MetricMetadata getMetadata(int index) {
+                if (metadataBuilder_ == null) {
+                    return metadata_.get(index);
+                } else {
+                    return metadataBuilder_.getMessage(index);
+                }
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder setMetadata(int index, Types.MetricMetadata value) {
+                if (metadataBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureMetadataIsMutable();
+                    metadata_.set(index, value);
+                    onChanged();
+                } else {
+                    metadataBuilder_.setMessage(index, value);
+                }
+                return this;
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder setMetadata(int index, Types.MetricMetadata.Builder builderForValue) {
+                if (metadataBuilder_ == null) {
+                    ensureMetadataIsMutable();
+                    metadata_.set(index, builderForValue.build());
+                    onChanged();
+                } else {
+                    metadataBuilder_.setMessage(index, builderForValue.build());
+                }
+                return this;
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder addMetadata(Types.MetricMetadata value) {
+                if (metadataBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureMetadataIsMutable();
+                    metadata_.add(value);
+                    onChanged();
+                } else {
+                    metadataBuilder_.addMessage(value);
+                }
+                return this;
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder addMetadata(int index, Types.MetricMetadata value) {
+                if (metadataBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureMetadataIsMutable();
+                    metadata_.add(index, value);
+                    onChanged();
+                } else {
+                    metadataBuilder_.addMessage(index, value);
+                }
+                return this;
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder addMetadata(Types.MetricMetadata.Builder builderForValue) {
+                if (metadataBuilder_ == null) {
+                    ensureMetadataIsMutable();
+                    metadata_.add(builderForValue.build());
+                    onChanged();
+                } else {
+                    metadataBuilder_.addMessage(builderForValue.build());
+                }
+                return this;
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder addMetadata(int index, Types.MetricMetadata.Builder builderForValue) {
+                if (metadataBuilder_ == null) {
+                    ensureMetadataIsMutable();
+                    metadata_.add(index, builderForValue.build());
+                    onChanged();
+                } else {
+                    metadataBuilder_.addMessage(index, builderForValue.build());
+                }
+                return this;
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder addAllMetadata(Iterable<? extends Types.MetricMetadata> values) {
+                if (metadataBuilder_ == null) {
+                    ensureMetadataIsMutable();
+                    com.google.protobuf.AbstractMessageLite.Builder.addAll(values, metadata_);
+                    onChanged();
+                } else {
+                    metadataBuilder_.addAllMessages(values);
+                }
+                return this;
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder clearMetadata() {
+                if (metadataBuilder_ == null) {
+                    metadata_ = java.util.Collections.emptyList();
+                    bitField0_ = (bitField0_ & ~0x00000002);
+                    onChanged();
+                } else {
+                    metadataBuilder_.clear();
+                }
+                return this;
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Builder removeMetadata(int index) {
+                if (metadataBuilder_ == null) {
+                    ensureMetadataIsMutable();
+                    metadata_.remove(index);
+                    onChanged();
+                } else {
+                    metadataBuilder_.remove(index);
+                }
+                return this;
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Types.MetricMetadata.Builder getMetadataBuilder(int index) {
+                return getMetadataFieldBuilder().getBuilder(index);
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Types.MetricMetadataOrBuilder getMetadataOrBuilder(int index) {
+                if (metadataBuilder_ == null) {
+                    return metadata_.get(index);
+                } else {
+                    return metadataBuilder_.getMessageOrBuilder(index);
+                }
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public java.util.List<? extends Types.MetricMetadataOrBuilder>
+                    getMetadataOrBuilderList() {
+                if (metadataBuilder_ != null) {
+                    return metadataBuilder_.getMessageOrBuilderList();
+                } else {
+                    return java.util.Collections.unmodifiableList(metadata_);
+                }
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Types.MetricMetadata.Builder addMetadataBuilder() {
+                return getMetadataFieldBuilder()
+                        .addBuilder(Types.MetricMetadata.getDefaultInstance());
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public Types.MetricMetadata.Builder addMetadataBuilder(int index) {
+                return getMetadataFieldBuilder()
+                        .addBuilder(index, Types.MetricMetadata.getDefaultInstance());
+            }
+
+            /**
+             * <code>
+             * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false];
+             * </code>
+             */
+            public java.util.List<Types.MetricMetadata.Builder> getMetadataBuilderList() {
+                return getMetadataFieldBuilder().getBuilderList();
+            }
+
+            private com.google.protobuf.RepeatedFieldBuilderV3<
+                            Types.MetricMetadata,
+                            Types.MetricMetadata.Builder,
+                            Types.MetricMetadataOrBuilder>
+                    getMetadataFieldBuilder() {
+                if (metadataBuilder_ == null) {
+                    metadataBuilder_ =
+                            new com.google.protobuf.RepeatedFieldBuilderV3<
+                                    Types.MetricMetadata,
+                                    Types.MetricMetadata.Builder,
+                                    Types.MetricMetadataOrBuilder>(
+                                    metadata_,
+                                    ((bitField0_ & 0x00000002) != 0),
+                                    getParentForChildren(),
+                                    isClean());
+                    metadata_ = null;
+                }
+                return metadataBuilder_;
+            }
+
+            @Override
+            public final Builder setUnknownFields(
+                    final com.google.protobuf.UnknownFieldSet unknownFields) {
+                return super.setUnknownFields(unknownFields);
+            }
+
+            @Override
+            public final Builder mergeUnknownFields(
+                    final com.google.protobuf.UnknownFieldSet unknownFields) {
+                return super.mergeUnknownFields(unknownFields);
+            }
+
+            // @@protoc_insertion_point(builder_scope:prometheus.WriteRequest)
+        }
+
+        // @@protoc_insertion_point(class_scope:prometheus.WriteRequest)
+        private static final WriteRequest DEFAULT_INSTANCE;
+
+        static {
+            DEFAULT_INSTANCE = new WriteRequest();
+        }
+
+        public static WriteRequest getDefaultInstance() {
+            return DEFAULT_INSTANCE;
+        }
+
+        private static final com.google.protobuf.Parser<WriteRequest> PARSER =
+                new com.google.protobuf.AbstractParser<WriteRequest>() {
+                    @Override
+                    public WriteRequest parsePartialFrom(
+                            com.google.protobuf.CodedInputStream input,
+                            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                            throws com.google.protobuf.InvalidProtocolBufferException {
+                        Builder builder = newBuilder();
+                        try {
+                            builder.mergeFrom(input, extensionRegistry);
+                        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+                            throw e.setUnfinishedMessage(builder.buildPartial());
+                        } catch (com.google.protobuf.UninitializedMessageException e) {
+                            throw e.asInvalidProtocolBufferException()
+                                    .setUnfinishedMessage(builder.buildPartial());
+                        } catch (java.io.IOException e) {
+                            throw new com.google.protobuf.InvalidProtocolBufferException(e)
+                                    .setUnfinishedMessage(builder.buildPartial());
+                        }
+                        return builder.buildPartial();
+                    }
+                };
+
+        public static com.google.protobuf.Parser<WriteRequest> parser() {
+            return PARSER;
+        }
+
+        @Override
+        public com.google.protobuf.Parser<WriteRequest> getParserForType() {
+            return PARSER;
+        }
+
+        @Override
+        public WriteRequest getDefaultInstanceForType() {
+            return DEFAULT_INSTANCE;
+        }
+    }
+
+    public interface ReadRequestOrBuilder
+            extends
+            // @@protoc_insertion_point(interface_extends:prometheus.ReadRequest)
+            com.google.protobuf.MessageOrBuilder {
+
+        /** <code>repeated .prometheus.Query queries = 1;</code> */
+        java.util.List<Query> getQueriesList();
+
+        /** <code>repeated .prometheus.Query queries = 1;</code> */
+        Query getQueries(int index);
+
+        /** <code>repeated .prometheus.Query queries = 1;</code> */
+        int getQueriesCount();
+
+        /** <code>repeated .prometheus.Query queries = 1;</code> */
+        java.util.List<? extends QueryOrBuilder> getQueriesOrBuilderList();
+
+        /** <code>repeated .prometheus.Query queries = 1;</code> */
+        QueryOrBuilder getQueriesOrBuilder(int index);
+
+        /**
+         *
+         *
+         * <pre>
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * </pre>
+         *
+         * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;</code>
+         *
+         * @return A list containing the acceptedResponseTypes.
+         */
+        java.util.List<ReadRequest.ResponseType> getAcceptedResponseTypesList();
+
+        /**
+         *
+         *
+         * <pre>
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * </pre>
+         *
+         * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;</code>
+         *
+         * @return The count of acceptedResponseTypes.
+         */
+        int getAcceptedResponseTypesCount();
+
+        /**
+         *
+         *
+         * <pre>
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * </pre>
+         *
+         * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;</code>
+         *
+         * @param index The index of the element to return.
+         * @return The acceptedResponseTypes at the given index.
+         */
+        ReadRequest.ResponseType getAcceptedResponseTypes(int index);
+
+        /**
+         *
+         *
+         * <pre>
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * </pre>
+         *
+         * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;</code>
+         *
+         * @return A list containing the enum numeric values on the wire for acceptedResponseTypes.
+         */
+        java.util.List<Integer> getAcceptedResponseTypesValueList();
+
+        /**
+         *
+         *
+         * <pre>
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * </pre>
+         *
+         * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;</code>
+         *
+         * @param index The index of the value to return.
+         * @return The enum numeric value on the wire of acceptedResponseTypes at the given index.
+         */
+        int getAcceptedResponseTypesValue(int index);
+    }
+
+    /**
+     *
+     *
+     * <pre>
+     * ReadRequest represents a remote read request.
+     * </pre>
+     *
+     * <p>Protobuf type {@code prometheus.ReadRequest}
+     */
+    public static final class ReadRequest extends com.google.protobuf.GeneratedMessageV3
+            implements
+            // @@protoc_insertion_point(message_implements:prometheus.ReadRequest)
+            ReadRequestOrBuilder {
+        private static final long serialVersionUID = 0L;
+
+        // Use ReadRequest.newBuilder() to construct.
+        private ReadRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+            super(builder);
+        }
+
+        private ReadRequest() {
+            queries_ = java.util.Collections.emptyList();
+            acceptedResponseTypes_ = java.util.Collections.emptyList();
+        }
+
+        @Override
+        @SuppressWarnings({"unused"})
+        protected Object newInstance(UnusedPrivateParameter unused) {
+            return new ReadRequest();
+        }
+
+        public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
+            return internal_static_prometheus_ReadRequest_descriptor;
+        }
+
+        @Override
+        protected FieldAccessorTable internalGetFieldAccessorTable() {
+            return internal_static_prometheus_ReadRequest_fieldAccessorTable
+                    .ensureFieldAccessorsInitialized(ReadRequest.class, Builder.class);
+        }
+
+        /** Protobuf enum {@code prometheus.ReadRequest.ResponseType} */
+        public enum ResponseType implements com.google.protobuf.ProtocolMessageEnum {
+            /**
+             *
+             *
+             * <pre>
+             * Server will return a single ReadResponse message with matched series that includes list of raw samples.
+             * It's recommended to use streamed response types instead.
+             *
+             * Response headers:
+             * Content-Type: "application/x-protobuf"
+             * Content-Encoding: "snappy"
+             * </pre>
+             *
+             * <code>SAMPLES = 0;</code>
+             */
+            SAMPLES(0),
+            /**
+             *
+             *
+             * <pre>
+             * Server will stream a delimited ChunkedReadResponse message that
+             * contains XOR or HISTOGRAM(!) encoded chunks for a single series.
+             * Each message is following varint size and fixed size bigendian
+             * uint32 for CRC32 Castagnoli checksum.
+             *
+             * Response headers:
+             * Content-Type: "application/x-streamed-protobuf; proto=prometheus.ChunkedReadResponse"
+             * Content-Encoding: ""
+             * </pre>
+             *
+             * <code>STREAMED_XOR_CHUNKS = 1;</code>
+             */
+            STREAMED_XOR_CHUNKS(1),
+            UNRECOGNIZED(-1),
+            ;
+
+            /**
+             *
+             *
+             * <pre>
+             * Server will return a single ReadResponse message with matched series that includes list of raw samples.
+             * It's recommended to use streamed response types instead.
+             *
+             * Response headers:
+             * Content-Type: "application/x-protobuf"
+             * Content-Encoding: "snappy"
+             * </pre>
+             *
+             * <code>SAMPLES = 0;</code>
+             */
+            public static final int SAMPLES_VALUE = 0;
+            /**
+             *
+             *
+             * <pre>
+             * Server will stream a delimited ChunkedReadResponse message that
+             * contains XOR or HISTOGRAM(!) encoded chunks for a single series.
+             * Each message is following varint size and fixed size bigendian
+             * uint32 for CRC32 Castagnoli checksum.
+             *
+             * Response headers:
+             * Content-Type: "application/x-streamed-protobuf; proto=prometheus.ChunkedReadResponse"
+             * Content-Encoding: ""
+             * </pre>
+             *
+             * <code>STREAMED_XOR_CHUNKS = 1;</code>
+             */
+            public static final int STREAMED_XOR_CHUNKS_VALUE = 1;
+
+            public final int getNumber() {
+                if (this == UNRECOGNIZED) {
+                    throw new IllegalArgumentException(
+                            "Can't get the number of an unknown enum value.");
+                }
+                return value;
+            }
+
+            /**
+             * @param value The numeric wire value of the corresponding enum entry.
+             * @return The enum associated with the given numeric wire value.
+             * @deprecated Use {@link #forNumber(int)} instead.
+             */
+            @Deprecated
+            public static ResponseType valueOf(int value) {
+                return forNumber(value);
+            }
+
+            /**
+             * @param value The numeric wire value of the corresponding enum entry.
+             * @return The enum associated with the given numeric wire value.
+             */
+            public static ResponseType forNumber(int value) {
+                switch (value) {
+                    case 0:
+                        return SAMPLES;
+                    case 1:
+                        return STREAMED_XOR_CHUNKS;
+                    default:
+                        return null;
+                }
+            }
+
+            public static com.google.protobuf.Internal.EnumLiteMap<ResponseType>
+                    internalGetValueMap() {
+                return internalValueMap;
+            }
+
+            private static final com.google.protobuf.Internal.EnumLiteMap<ResponseType>
+                    internalValueMap =
+                            new com.google.protobuf.Internal.EnumLiteMap<ResponseType>() {
+                                public ResponseType findValueByNumber(int number) {
+                                    return ResponseType.forNumber(number);
+                                }
+                            };
+
+            public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() {
+                if (this == UNRECOGNIZED) {
+                    throw new IllegalStateException(
+                            "Can't get the descriptor of an unrecognized enum value.");
+                }
+                return getDescriptor().getValues().get(ordinal());
+            }
+
+            public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() {
+                return getDescriptor();
+            }
+
+            public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() {
+                return ReadRequest.getDescriptor().getEnumTypes().get(0);
+            }
+
+            private static final ResponseType[] VALUES = values();
+
+            public static ResponseType valueOf(
+                    com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+                if (desc.getType() != getDescriptor()) {
+                    throw new IllegalArgumentException("EnumValueDescriptor is not for this type.");
+                }
+                if (desc.getIndex() == -1) {
+                    return UNRECOGNIZED;
+                }
+                return VALUES[desc.getIndex()];
+            }
+
+            private final int value;
+
+            private ResponseType(int value) {
+                this.value = value;
+            }
+
+            // @@protoc_insertion_point(enum_scope:prometheus.ReadRequest.ResponseType)
+        }
+
+        public static final int QUERIES_FIELD_NUMBER = 1;
+
+        @SuppressWarnings("serial")
+        private java.util.List<Query> queries_;
+
+        /** <code>repeated .prometheus.Query queries = 1;</code> */
+        @Override
+        public java.util.List<Query> getQueriesList() {
+            return queries_;
+        }
+
+        /** <code>repeated .prometheus.Query queries = 1;</code> */
+        @Override
+        public java.util.List<? extends QueryOrBuilder> getQueriesOrBuilderList() {
+            return queries_;
+        }
+
+        /** <code>repeated .prometheus.Query queries = 1;</code> */
+        @Override
+        public int getQueriesCount() {
+            return queries_.size();
+        }
+
+        /** <code>repeated .prometheus.Query queries = 1;</code> */
+        @Override
+        public Query getQueries(int index) {
+            return queries_.get(index);
+        }
+
+        /** <code>repeated .prometheus.Query queries = 1;</code> */
+        @Override
+        public QueryOrBuilder getQueriesOrBuilder(int index) {
+            return queries_.get(index);
+        }
+
+        public static final int ACCEPTED_RESPONSE_TYPES_FIELD_NUMBER = 2;
+
+        @SuppressWarnings("serial")
+        private java.util.List<Integer> acceptedResponseTypes_;
+
+        private static final com.google.protobuf.Internal.ListAdapter.Converter<
+                        Integer, ResponseType>
+                acceptedResponseTypes_converter_ =
+                        new com.google.protobuf.Internal.ListAdapter.Converter<
+                                Integer, ResponseType>() {
+                            public ResponseType convert(Integer from) {
+                                ResponseType result = ResponseType.forNumber(from);
+                                return result == null ? ResponseType.UNRECOGNIZED : result;
+                            }
+                        };
+
+        /**
+         *
+         *
+         * <pre>
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * </pre>
+         *
+         * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;</code>
+         *
+         * @return A list containing the acceptedResponseTypes.
+         */
+        @Override
+        public java.util.List<ResponseType> getAcceptedResponseTypesList() {
+            return new com.google.protobuf.Internal.ListAdapter<Integer, ResponseType>(
+                    acceptedResponseTypes_, acceptedResponseTypes_converter_);
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * </pre>
+         *
+         * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;</code>
+         *
+         * @return The count of acceptedResponseTypes.
+         */
+        @Override
+        public int getAcceptedResponseTypesCount() {
+            return acceptedResponseTypes_.size();
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * </pre>
+         *
+         * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;</code>
+         *
+         * @param index The index of the element to return.
+         * @return The acceptedResponseTypes at the given index.
+         */
+        @Override
+        public ResponseType getAcceptedResponseTypes(int index) {
+            return acceptedResponseTypes_converter_.convert(acceptedResponseTypes_.get(index));
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * </pre>
+         *
+         * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;</code>
+         *
+         * @return A list containing the enum numeric values on the wire for acceptedResponseTypes.
+         */
+        @Override
+        public java.util.List<Integer> getAcceptedResponseTypesValueList() {
+            return acceptedResponseTypes_;
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * </pre>
+         *
+         * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;</code>
+         *
+         * @param index The index of the value to return.
+         * @return The enum numeric value on the wire of acceptedResponseTypes at the given index.
+         */
+        @Override
+        public int getAcceptedResponseTypesValue(int index) {
+            return acceptedResponseTypes_.get(index);
+        }
+
+        private int acceptedResponseTypesMemoizedSerializedSize;
+
+        private byte memoizedIsInitialized = -1;
+
+        @Override
+        public final boolean isInitialized() {
+            byte isInitialized = memoizedIsInitialized;
+            if (isInitialized == 1) return true;
+            if (isInitialized == 0) return false;
+
+            memoizedIsInitialized = 1;
+            return true;
+        }
+
+        @Override
+        public void writeTo(com.google.protobuf.CodedOutputStream output)
+                throws java.io.IOException {
+            getSerializedSize();
+            for (int i = 0; i < queries_.size(); i++) {
+                output.writeMessage(1, queries_.get(i));
+            }
+            if (getAcceptedResponseTypesList().size() > 0) {
+                output.writeUInt32NoTag(18);
+                output.writeUInt32NoTag(acceptedResponseTypesMemoizedSerializedSize);
+            }
+            for (int i = 0; i < acceptedResponseTypes_.size(); i++) {
+                output.writeEnumNoTag(acceptedResponseTypes_.get(i));
+            }
+            getUnknownFields().writeTo(output);
+        }
+
+        @Override
+        public int getSerializedSize() {
+            int size = memoizedSize;
+            if (size != -1) return size;
+
+            size = 0;
+            for (int i = 0; i < queries_.size(); i++) {
+                size +=
+                        com.google.protobuf.CodedOutputStream.computeMessageSize(
+                                1, queries_.get(i));
+            }
+            {
+                int dataSize = 0;
+                for (int i = 0; i < acceptedResponseTypes_.size(); i++) {
+                    dataSize +=
+                            com.google.protobuf.CodedOutputStream.computeEnumSizeNoTag(
+                                    acceptedResponseTypes_.get(i));
+                }
+                size += dataSize;
+                if (!getAcceptedResponseTypesList().isEmpty()) {
+                    size += 1;
+                    size += com.google.protobuf.CodedOutputStream.computeUInt32SizeNoTag(dataSize);
+                }
+                acceptedResponseTypesMemoizedSerializedSize = dataSize;
+            }
+            size += getUnknownFields().getSerializedSize();
+            memoizedSize = size;
+            return size;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof ReadRequest)) {
+                return super.equals(obj);
+            }
+            ReadRequest other = (ReadRequest) obj;
+
+            if (!getQueriesList().equals(other.getQueriesList())) return false;
+            if (!acceptedResponseTypes_.equals(other.acceptedResponseTypes_)) return false;
+            if (!getUnknownFields().equals(other.getUnknownFields())) return false;
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            if (memoizedHashCode != 0) {
+                return memoizedHashCode;
+            }
+            int hash = 41;
+            hash = (19 * hash) + getDescriptor().hashCode();
+            if (getQueriesCount() > 0) {
+                hash = (37 * hash) + QUERIES_FIELD_NUMBER;
+                hash = (53 * hash) + getQueriesList().hashCode();
+            }
+            if (getAcceptedResponseTypesCount() > 0) {
+                hash = (37 * hash) + ACCEPTED_RESPONSE_TYPES_FIELD_NUMBER;
+                hash = (53 * hash) + acceptedResponseTypes_.hashCode();
+            }
+            hash = (29 * hash) + getUnknownFields().hashCode();
+            memoizedHashCode = hash;
+            return hash;
+        }
+
+        public static ReadRequest parseFrom(java.nio.ByteBuffer data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static ReadRequest parseFrom(
+                java.nio.ByteBuffer data,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static ReadRequest parseFrom(com.google.protobuf.ByteString data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static ReadRequest parseFrom(
+                com.google.protobuf.ByteString data,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static ReadRequest parseFrom(byte[] data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static ReadRequest parseFrom(
+                byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static ReadRequest parseFrom(java.io.InputStream input) throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
+        }
+
+        public static ReadRequest parseFrom(
+                java.io.InputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        public static ReadRequest parseDelimitedFrom(java.io.InputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(
+                    PARSER, input);
+        }
+
+        public static ReadRequest parseDelimitedFrom(
+                java.io.InputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        public static ReadRequest parseFrom(com.google.protobuf.CodedInputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
+        }
+
+        public static ReadRequest parseFrom(
+                com.google.protobuf.CodedInputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        @Override
+        public Builder newBuilderForType() {
+            return newBuilder();
+        }
+
+        public static Builder newBuilder() {
+            return DEFAULT_INSTANCE.toBuilder();
+        }
+
+        public static Builder newBuilder(ReadRequest prototype) {
+            return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+        }
+
+        @Override
+        public Builder toBuilder() {
+            return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this);
+        }
+
+        @Override
+        protected Builder newBuilderForType(BuilderParent parent) {
+            Builder builder = new Builder(parent);
+            return builder;
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * ReadRequest represents a remote read request.
+         * </pre>
+         *
+         * <p>Protobuf type {@code prometheus.ReadRequest}
+         */
+        public static final class Builder
+                extends com.google.protobuf.GeneratedMessageV3.Builder<Builder>
+                implements
+                // @@protoc_insertion_point(builder_implements:prometheus.ReadRequest)
+                ReadRequestOrBuilder {
+            public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
+                return internal_static_prometheus_ReadRequest_descriptor;
+            }
+
+            @Override
+            protected FieldAccessorTable internalGetFieldAccessorTable() {
+                return internal_static_prometheus_ReadRequest_fieldAccessorTable
+                        .ensureFieldAccessorsInitialized(ReadRequest.class, Builder.class);
+            }
+
+            // Construct using
+            // org.apache.flink.connector.prometheus.sink.prometheus.Remote.ReadRequest.newBuilder()
+            private Builder() {}
+
+            private Builder(BuilderParent parent) {
+                super(parent);
+            }
+
+            @Override
+            public Builder clear() {
+                super.clear();
+                bitField0_ = 0;
+                if (queriesBuilder_ == null) {
+                    queries_ = java.util.Collections.emptyList();
+                } else {
+                    queries_ = null;
+                    queriesBuilder_.clear();
+                }
+                bitField0_ = (bitField0_ & ~0x00000001);
+                acceptedResponseTypes_ = java.util.Collections.emptyList();
+                bitField0_ = (bitField0_ & ~0x00000002);
+                return this;
+            }
+
+            @Override
+            public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() {
+                return internal_static_prometheus_ReadRequest_descriptor;
+            }
+
+            @Override
+            public ReadRequest getDefaultInstanceForType() {
+                return getDefaultInstance();
+            }
+
+            @Override
+            public ReadRequest build() {
+                ReadRequest result = buildPartial();
+                if (!result.isInitialized()) {
+                    throw newUninitializedMessageException(result);
+                }
+                return result;
+            }
+
+            @Override
+            public ReadRequest buildPartial() {
+                ReadRequest result = new ReadRequest(this);
+                buildPartialRepeatedFields(result);
+                if (bitField0_ != 0) {
+                    buildPartial0(result);
+                }
+                onBuilt();
+                return result;
+            }
+
+            private void buildPartialRepeatedFields(ReadRequest result) {
+                if (queriesBuilder_ == null) {
+                    if (((bitField0_ & 0x00000001) != 0)) {
+                        queries_ = java.util.Collections.unmodifiableList(queries_);
+                        bitField0_ = (bitField0_ & ~0x00000001);
+                    }
+                    result.queries_ = queries_;
+                } else {
+                    result.queries_ = queriesBuilder_.build();
+                }
+                if (((bitField0_ & 0x00000002) != 0)) {
+                    acceptedResponseTypes_ =
+                            java.util.Collections.unmodifiableList(acceptedResponseTypes_);
+                    bitField0_ = (bitField0_ & ~0x00000002);
+                }
+                result.acceptedResponseTypes_ = acceptedResponseTypes_;
+            }
+
+            private void buildPartial0(ReadRequest result) {
+                int from_bitField0_ = bitField0_;
+            }
+
+            @Override
+            public Builder mergeFrom(com.google.protobuf.Message other) {
+                if (other instanceof ReadRequest) {
+                    return mergeFrom((ReadRequest) other);
+                } else {
+                    super.mergeFrom(other);
+                    return this;
+                }
+            }
+
+            public Builder mergeFrom(ReadRequest other) {
+                if (other == getDefaultInstance()) return this;
+                if (queriesBuilder_ == null) {
+                    if (!other.queries_.isEmpty()) {
+                        if (queries_.isEmpty()) {
+                            queries_ = other.queries_;
+                            bitField0_ = (bitField0_ & ~0x00000001);
+                        } else {
+                            ensureQueriesIsMutable();
+                            queries_.addAll(other.queries_);
+                        }
+                        onChanged();
+                    }
+                } else {
+                    if (!other.queries_.isEmpty()) {
+                        if (queriesBuilder_.isEmpty()) {
+                            queriesBuilder_.dispose();
+                            queriesBuilder_ = null;
+                            queries_ = other.queries_;
+                            bitField0_ = (bitField0_ & ~0x00000001);
+                            queriesBuilder_ =
+                                    com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders
+                                            ? getQueriesFieldBuilder()
+                                            : null;
+                        } else {
+                            queriesBuilder_.addAllMessages(other.queries_);
+                        }
+                    }
+                }
+                if (!other.acceptedResponseTypes_.isEmpty()) {
+                    if (acceptedResponseTypes_.isEmpty()) {
+                        acceptedResponseTypes_ = other.acceptedResponseTypes_;
+                        bitField0_ = (bitField0_ & ~0x00000002);
+                    } else {
+                        ensureAcceptedResponseTypesIsMutable();
+                        acceptedResponseTypes_.addAll(other.acceptedResponseTypes_);
+                    }
+                    onChanged();
+                }
+                this.mergeUnknownFields(other.getUnknownFields());
+                onChanged();
+                return this;
+            }
+
+            @Override
+            public final boolean isInitialized() {
+                return true;
+            }
+
+            @Override
+            public Builder mergeFrom(
+                    com.google.protobuf.CodedInputStream input,
+                    com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                    throws java.io.IOException {
+                if (extensionRegistry == null) {
+                    throw new NullPointerException();
+                }
+                try {
+                    boolean done = false;
+                    while (!done) {
+                        int tag = input.readTag();
+                        switch (tag) {
+                            case 0:
+                                done = true;
+                                break;
+                            case 10:
+                                {
+                                    Query m = input.readMessage(Query.parser(), extensionRegistry);
+                                    if (queriesBuilder_ == null) {
+                                        ensureQueriesIsMutable();
+                                        queries_.add(m);
+                                    } else {
+                                        queriesBuilder_.addMessage(m);
+                                    }
+                                    break;
+                                } // case 10
+                            case 16:
+                                {
+                                    int tmpRaw = input.readEnum();
+                                    ensureAcceptedResponseTypesIsMutable();
+                                    acceptedResponseTypes_.add(tmpRaw);
+                                    break;
+                                } // case 16
+                            case 18:
+                                {
+                                    int length = input.readRawVarint32();
+                                    int oldLimit = input.pushLimit(length);
+                                    while (input.getBytesUntilLimit() > 0) {
+                                        int tmpRaw = input.readEnum();
+                                        ensureAcceptedResponseTypesIsMutable();
+                                        acceptedResponseTypes_.add(tmpRaw);
+                                    }
+                                    input.popLimit(oldLimit);
+                                    break;
+                                } // case 18
+                            default:
+                                {
+                                    if (!super.parseUnknownField(input, extensionRegistry, tag)) {
+                                        done = true; // was an endgroup tag
+                                    }
+                                    break;
+                                } // default:
+                        } // switch (tag)
+                    } // while (!done)
+                } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+                    throw e.unwrapIOException();
+                } finally {
+                    onChanged();
+                } // finally
+                return this;
+            }
+
+            private int bitField0_;
+
+            private java.util.List<Query> queries_ = java.util.Collections.emptyList();
+
+            private void ensureQueriesIsMutable() {
+                if (!((bitField0_ & 0x00000001) != 0)) {
+                    queries_ = new java.util.ArrayList<Query>(queries_);
+                    bitField0_ |= 0x00000001;
+                }
+            }
+
+            private com.google.protobuf.RepeatedFieldBuilderV3<Query, Query.Builder, QueryOrBuilder>
+                    queriesBuilder_;
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public java.util.List<Query> getQueriesList() {
+                if (queriesBuilder_ == null) {
+                    return java.util.Collections.unmodifiableList(queries_);
+                } else {
+                    return queriesBuilder_.getMessageList();
+                }
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public int getQueriesCount() {
+                if (queriesBuilder_ == null) {
+                    return queries_.size();
+                } else {
+                    return queriesBuilder_.getCount();
+                }
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Query getQueries(int index) {
+                if (queriesBuilder_ == null) {
+                    return queries_.get(index);
+                } else {
+                    return queriesBuilder_.getMessage(index);
+                }
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Builder setQueries(int index, Query value) {
+                if (queriesBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureQueriesIsMutable();
+                    queries_.set(index, value);
+                    onChanged();
+                } else {
+                    queriesBuilder_.setMessage(index, value);
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Builder setQueries(int index, Query.Builder builderForValue) {
+                if (queriesBuilder_ == null) {
+                    ensureQueriesIsMutable();
+                    queries_.set(index, builderForValue.build());
+                    onChanged();
+                } else {
+                    queriesBuilder_.setMessage(index, builderForValue.build());
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Builder addQueries(Query value) {
+                if (queriesBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureQueriesIsMutable();
+                    queries_.add(value);
+                    onChanged();
+                } else {
+                    queriesBuilder_.addMessage(value);
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Builder addQueries(int index, Query value) {
+                if (queriesBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureQueriesIsMutable();
+                    queries_.add(index, value);
+                    onChanged();
+                } else {
+                    queriesBuilder_.addMessage(index, value);
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Builder addQueries(Query.Builder builderForValue) {
+                if (queriesBuilder_ == null) {
+                    ensureQueriesIsMutable();
+                    queries_.add(builderForValue.build());
+                    onChanged();
+                } else {
+                    queriesBuilder_.addMessage(builderForValue.build());
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Builder addQueries(int index, Query.Builder builderForValue) {
+                if (queriesBuilder_ == null) {
+                    ensureQueriesIsMutable();
+                    queries_.add(index, builderForValue.build());
+                    onChanged();
+                } else {
+                    queriesBuilder_.addMessage(index, builderForValue.build());
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Builder addAllQueries(Iterable<? extends Query> values) {
+                if (queriesBuilder_ == null) {
+                    ensureQueriesIsMutable();
+                    com.google.protobuf.AbstractMessageLite.Builder.addAll(values, queries_);
+                    onChanged();
+                } else {
+                    queriesBuilder_.addAllMessages(values);
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Builder clearQueries() {
+                if (queriesBuilder_ == null) {
+                    queries_ = java.util.Collections.emptyList();
+                    bitField0_ = (bitField0_ & ~0x00000001);
+                    onChanged();
+                } else {
+                    queriesBuilder_.clear();
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Builder removeQueries(int index) {
+                if (queriesBuilder_ == null) {
+                    ensureQueriesIsMutable();
+                    queries_.remove(index);
+                    onChanged();
+                } else {
+                    queriesBuilder_.remove(index);
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Query.Builder getQueriesBuilder(int index) {
+                return getQueriesFieldBuilder().getBuilder(index);
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public QueryOrBuilder getQueriesOrBuilder(int index) {
+                if (queriesBuilder_ == null) {
+                    return queries_.get(index);
+                } else {
+                    return queriesBuilder_.getMessageOrBuilder(index);
+                }
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public java.util.List<? extends QueryOrBuilder> getQueriesOrBuilderList() {
+                if (queriesBuilder_ != null) {
+                    return queriesBuilder_.getMessageOrBuilderList();
+                } else {
+                    return java.util.Collections.unmodifiableList(queries_);
+                }
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Query.Builder addQueriesBuilder() {
+                return getQueriesFieldBuilder().addBuilder(Query.getDefaultInstance());
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public Query.Builder addQueriesBuilder(int index) {
+                return getQueriesFieldBuilder().addBuilder(index, Query.getDefaultInstance());
+            }
+
+            /** <code>repeated .prometheus.Query queries = 1;</code> */
+            public java.util.List<Query.Builder> getQueriesBuilderList() {
+                return getQueriesFieldBuilder().getBuilderList();
+            }
+
+            private com.google.protobuf.RepeatedFieldBuilderV3<Query, Query.Builder, QueryOrBuilder>
+                    getQueriesFieldBuilder() {
+                if (queriesBuilder_ == null) {
+                    queriesBuilder_ =
+                            new com.google.protobuf.RepeatedFieldBuilderV3<
+                                    Query, Query.Builder, QueryOrBuilder>(
+                                    queries_,
+                                    ((bitField0_ & 0x00000001) != 0),
+                                    getParentForChildren(),
+                                    isClean());
+                    queries_ = null;
+                }
+                return queriesBuilder_;
+            }
+
+            private java.util.List<Integer> acceptedResponseTypes_ =
+                    java.util.Collections.emptyList();
+
+            private void ensureAcceptedResponseTypesIsMutable() {
+                if (!((bitField0_ & 0x00000002) != 0)) {
+                    acceptedResponseTypes_ =
+                            new java.util.ArrayList<Integer>(acceptedResponseTypes_);
+                    bitField0_ |= 0x00000002;
+                }
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * </pre>
+             *
+             * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;
+             * </code>
+             *
+             * @return A list containing the acceptedResponseTypes.
+             */
+            public java.util.List<ResponseType> getAcceptedResponseTypesList() {
+                return new com.google.protobuf.Internal.ListAdapter<Integer, ResponseType>(
+                        acceptedResponseTypes_, acceptedResponseTypes_converter_);
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * </pre>
+             *
+             * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;
+             * </code>
+             *
+             * @return The count of acceptedResponseTypes.
+             */
+            public int getAcceptedResponseTypesCount() {
+                return acceptedResponseTypes_.size();
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * </pre>
+             *
+             * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;
+             * </code>
+             *
+             * @param index The index of the element to return.
+             * @return The acceptedResponseTypes at the given index.
+             */
+            public ResponseType getAcceptedResponseTypes(int index) {
+                return acceptedResponseTypes_converter_.convert(acceptedResponseTypes_.get(index));
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * </pre>
+             *
+             * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;
+             * </code>
+             *
+             * @param index The index to set the value at.
+             * @param value The acceptedResponseTypes to set.
+             * @return This builder for chaining.
+             */
+            public Builder setAcceptedResponseTypes(int index, ResponseType value) {
+                if (value == null) {
+                    throw new NullPointerException();
+                }
+                ensureAcceptedResponseTypesIsMutable();
+                acceptedResponseTypes_.set(index, value.getNumber());
+                onChanged();
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * </pre>
+             *
+             * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;
+             * </code>
+             *
+             * @param value The acceptedResponseTypes to add.
+             * @return This builder for chaining.
+             */
+            public Builder addAcceptedResponseTypes(ResponseType value) {
+                if (value == null) {
+                    throw new NullPointerException();
+                }
+                ensureAcceptedResponseTypesIsMutable();
+                acceptedResponseTypes_.add(value.getNumber());
+                onChanged();
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * </pre>
+             *
+             * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;
+             * </code>
+             *
+             * @param values The acceptedResponseTypes to add.
+             * @return This builder for chaining.
+             */
+            public Builder addAllAcceptedResponseTypes(Iterable<? extends ResponseType> values) {
+                ensureAcceptedResponseTypesIsMutable();
+                for (ResponseType value : values) {
+                    acceptedResponseTypes_.add(value.getNumber());
+                }
+                onChanged();
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * </pre>
+             *
+             * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;
+             * </code>
+             *
+             * @return This builder for chaining.
+             */
+            public Builder clearAcceptedResponseTypes() {
+                acceptedResponseTypes_ = java.util.Collections.emptyList();
+                bitField0_ = (bitField0_ & ~0x00000002);
+                onChanged();
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * </pre>
+             *
+             * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;
+             * </code>
+             *
+             * @return A list containing the enum numeric values on the wire for
+             *     acceptedResponseTypes.
+             */
+            public java.util.List<Integer> getAcceptedResponseTypesValueList() {
+                return java.util.Collections.unmodifiableList(acceptedResponseTypes_);
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * </pre>
+             *
+             * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;
+             * </code>
+             *
+             * @param index The index of the value to return.
+             * @return The enum numeric value on the wire of acceptedResponseTypes at the given
+             *     index.
+             */
+            public int getAcceptedResponseTypesValue(int index) {
+                return acceptedResponseTypes_.get(index);
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * </pre>
+             *
+             * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;
+             * </code>
+             *
+             * @param index The index to set the value at.
+             * @param value The enum numeric value on the wire for acceptedResponseTypes to set.
+             * @return This builder for chaining.
+             */
+            public Builder setAcceptedResponseTypesValue(int index, int value) {
+                ensureAcceptedResponseTypesIsMutable();
+                acceptedResponseTypes_.set(index, value);
+                onChanged();
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * </pre>
+             *
+             * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;
+             * </code>
+             *
+             * @param value The enum numeric value on the wire for acceptedResponseTypes to add.
+             * @return This builder for chaining.
+             */
+            public Builder addAcceptedResponseTypesValue(int value) {
+                ensureAcceptedResponseTypesIsMutable();
+                acceptedResponseTypes_.add(value);
+                onChanged();
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * </pre>
+             *
+             * <code>repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2;
+             * </code>
+             *
+             * @param values The enum numeric values on the wire for acceptedResponseTypes to add.
+             * @return This builder for chaining.
+             */
+            public Builder addAllAcceptedResponseTypesValue(Iterable<Integer> values) {
+                ensureAcceptedResponseTypesIsMutable();
+                for (int value : values) {
+                    acceptedResponseTypes_.add(value);
+                }
+                onChanged();
+                return this;
+            }
+
+            @Override
+            public final Builder setUnknownFields(
+                    final com.google.protobuf.UnknownFieldSet unknownFields) {
+                return super.setUnknownFields(unknownFields);
+            }
+
+            @Override
+            public final Builder mergeUnknownFields(
+                    final com.google.protobuf.UnknownFieldSet unknownFields) {
+                return super.mergeUnknownFields(unknownFields);
+            }
+
+            // @@protoc_insertion_point(builder_scope:prometheus.ReadRequest)
+        }
+
+        // @@protoc_insertion_point(class_scope:prometheus.ReadRequest)
+        private static final ReadRequest DEFAULT_INSTANCE;
+
+        static {
+            DEFAULT_INSTANCE = new ReadRequest();
+        }
+
+        public static ReadRequest getDefaultInstance() {
+            return DEFAULT_INSTANCE;
+        }
+
+        private static final com.google.protobuf.Parser<ReadRequest> PARSER =
+                new com.google.protobuf.AbstractParser<ReadRequest>() {
+                    @Override
+                    public ReadRequest parsePartialFrom(
+                            com.google.protobuf.CodedInputStream input,
+                            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                            throws com.google.protobuf.InvalidProtocolBufferException {
+                        Builder builder = newBuilder();
+                        try {
+                            builder.mergeFrom(input, extensionRegistry);
+                        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+                            throw e.setUnfinishedMessage(builder.buildPartial());
+                        } catch (com.google.protobuf.UninitializedMessageException e) {
+                            throw e.asInvalidProtocolBufferException()
+                                    .setUnfinishedMessage(builder.buildPartial());
+                        } catch (java.io.IOException e) {
+                            throw new com.google.protobuf.InvalidProtocolBufferException(e)
+                                    .setUnfinishedMessage(builder.buildPartial());
+                        }
+                        return builder.buildPartial();
+                    }
+                };
+
+        public static com.google.protobuf.Parser<ReadRequest> parser() {
+            return PARSER;
+        }
+
+        @Override
+        public com.google.protobuf.Parser<ReadRequest> getParserForType() {
+            return PARSER;
+        }
+
+        @Override
+        public ReadRequest getDefaultInstanceForType() {
+            return DEFAULT_INSTANCE;
+        }
+    }
+
+    public interface ReadResponseOrBuilder
+            extends
+            // @@protoc_insertion_point(interface_extends:prometheus.ReadResponse)
+            com.google.protobuf.MessageOrBuilder {
+
+        /**
+         *
+         *
+         * <pre>
+         * In same order as the request's queries.
+         * </pre>
+         *
+         * <code>repeated .prometheus.QueryResult results = 1;</code>
+         */
+        java.util.List<QueryResult> getResultsList();
+
+        /**
+         *
+         *
+         * <pre>
+         * In same order as the request's queries.
+         * </pre>
+         *
+         * <code>repeated .prometheus.QueryResult results = 1;</code>
+         */
+        QueryResult getResults(int index);
+
+        /**
+         *
+         *
+         * <pre>
+         * In same order as the request's queries.
+         * </pre>
+         *
+         * <code>repeated .prometheus.QueryResult results = 1;</code>
+         */
+        int getResultsCount();
+
+        /**
+         *
+         *
+         * <pre>
+         * In same order as the request's queries.
+         * </pre>
+         *
+         * <code>repeated .prometheus.QueryResult results = 1;</code>
+         */
+        java.util.List<? extends QueryResultOrBuilder> getResultsOrBuilderList();
+
+        /**
+         *
+         *
+         * <pre>
+         * In same order as the request's queries.
+         * </pre>
+         *
+         * <code>repeated .prometheus.QueryResult results = 1;</code>
+         */
+        QueryResultOrBuilder getResultsOrBuilder(int index);
+    }
+
+    /**
+     *
+     *
+     * <pre>
+     * ReadResponse is a response when response_type equals SAMPLES.
+     * </pre>
+     *
+     * <p>Protobuf type {@code prometheus.ReadResponse}
+     */
+    public static final class ReadResponse extends com.google.protobuf.GeneratedMessageV3
+            implements
+            // @@protoc_insertion_point(message_implements:prometheus.ReadResponse)
+            ReadResponseOrBuilder {
+        private static final long serialVersionUID = 0L;
+
+        // Use ReadResponse.newBuilder() to construct.
+        private ReadResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+            super(builder);
+        }
+
+        private ReadResponse() {
+            results_ = java.util.Collections.emptyList();
+        }
+
+        @Override
+        @SuppressWarnings({"unused"})
+        protected Object newInstance(UnusedPrivateParameter unused) {
+            return new ReadResponse();
+        }
+
+        public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
+            return internal_static_prometheus_ReadResponse_descriptor;
+        }
+
+        @Override
+        protected FieldAccessorTable internalGetFieldAccessorTable() {
+            return internal_static_prometheus_ReadResponse_fieldAccessorTable
+                    .ensureFieldAccessorsInitialized(ReadResponse.class, Builder.class);
+        }
+
+        public static final int RESULTS_FIELD_NUMBER = 1;
+
+        @SuppressWarnings("serial")
+        private java.util.List<QueryResult> results_;
+
+        /**
+         *
+         *
+         * <pre>
+         * In same order as the request's queries.
+         * </pre>
+         *
+         * <code>repeated .prometheus.QueryResult results = 1;</code>
+         */
+        @Override
+        public java.util.List<QueryResult> getResultsList() {
+            return results_;
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * In same order as the request's queries.
+         * </pre>
+         *
+         * <code>repeated .prometheus.QueryResult results = 1;</code>
+         */
+        @Override
+        public java.util.List<? extends QueryResultOrBuilder> getResultsOrBuilderList() {
+            return results_;
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * In same order as the request's queries.
+         * </pre>
+         *
+         * <code>repeated .prometheus.QueryResult results = 1;</code>
+         */
+        @Override
+        public int getResultsCount() {
+            return results_.size();
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * In same order as the request's queries.
+         * </pre>
+         *
+         * <code>repeated .prometheus.QueryResult results = 1;</code>
+         */
+        @Override
+        public QueryResult getResults(int index) {
+            return results_.get(index);
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * In same order as the request's queries.
+         * </pre>
+         *
+         * <code>repeated .prometheus.QueryResult results = 1;</code>
+         */
+        @Override
+        public QueryResultOrBuilder getResultsOrBuilder(int index) {
+            return results_.get(index);
+        }
+
+        private byte memoizedIsInitialized = -1;
+
+        @Override
+        public final boolean isInitialized() {
+            byte isInitialized = memoizedIsInitialized;
+            if (isInitialized == 1) return true;
+            if (isInitialized == 0) return false;
+
+            memoizedIsInitialized = 1;
+            return true;
+        }
+
+        @Override
+        public void writeTo(com.google.protobuf.CodedOutputStream output)
+                throws java.io.IOException {
+            for (int i = 0; i < results_.size(); i++) {
+                output.writeMessage(1, results_.get(i));
+            }
+            getUnknownFields().writeTo(output);
+        }
+
+        @Override
+        public int getSerializedSize() {
+            int size = memoizedSize;
+            if (size != -1) return size;
+
+            size = 0;
+            for (int i = 0; i < results_.size(); i++) {
+                size +=
+                        com.google.protobuf.CodedOutputStream.computeMessageSize(
+                                1, results_.get(i));
+            }
+            size += getUnknownFields().getSerializedSize();
+            memoizedSize = size;
+            return size;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof ReadResponse)) {
+                return super.equals(obj);
+            }
+            ReadResponse other = (ReadResponse) obj;
+
+            if (!getResultsList().equals(other.getResultsList())) return false;
+            if (!getUnknownFields().equals(other.getUnknownFields())) return false;
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            if (memoizedHashCode != 0) {
+                return memoizedHashCode;
+            }
+            int hash = 41;
+            hash = (19 * hash) + getDescriptor().hashCode();
+            if (getResultsCount() > 0) {
+                hash = (37 * hash) + RESULTS_FIELD_NUMBER;
+                hash = (53 * hash) + getResultsList().hashCode();
+            }
+            hash = (29 * hash) + getUnknownFields().hashCode();
+            memoizedHashCode = hash;
+            return hash;
+        }
+
+        public static ReadResponse parseFrom(java.nio.ByteBuffer data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static ReadResponse parseFrom(
+                java.nio.ByteBuffer data,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static ReadResponse parseFrom(com.google.protobuf.ByteString data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static ReadResponse parseFrom(
+                com.google.protobuf.ByteString data,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static ReadResponse parseFrom(byte[] data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static ReadResponse parseFrom(
+                byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static ReadResponse parseFrom(java.io.InputStream input) throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
+        }
+
+        public static ReadResponse parseFrom(
+                java.io.InputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        public static ReadResponse parseDelimitedFrom(java.io.InputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(
+                    PARSER, input);
+        }
+
+        public static ReadResponse parseDelimitedFrom(
+                java.io.InputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        public static ReadResponse parseFrom(com.google.protobuf.CodedInputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
+        }
+
+        public static ReadResponse parseFrom(
+                com.google.protobuf.CodedInputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        @Override
+        public Builder newBuilderForType() {
+            return newBuilder();
+        }
+
+        public static Builder newBuilder() {
+            return DEFAULT_INSTANCE.toBuilder();
+        }
+
+        public static Builder newBuilder(ReadResponse prototype) {
+            return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+        }
+
+        @Override
+        public Builder toBuilder() {
+            return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this);
+        }
+
+        @Override
+        protected Builder newBuilderForType(BuilderParent parent) {
+            Builder builder = new Builder(parent);
+            return builder;
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * ReadResponse is a response when response_type equals SAMPLES.
+         * </pre>
+         *
+         * <p>Protobuf type {@code prometheus.ReadResponse}
+         */
+        public static final class Builder
+                extends com.google.protobuf.GeneratedMessageV3.Builder<Builder>
+                implements
+                // @@protoc_insertion_point(builder_implements:prometheus.ReadResponse)
+                ReadResponseOrBuilder {
+            public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
+                return internal_static_prometheus_ReadResponse_descriptor;
+            }
+
+            @Override
+            protected FieldAccessorTable internalGetFieldAccessorTable() {
+                return internal_static_prometheus_ReadResponse_fieldAccessorTable
+                        .ensureFieldAccessorsInitialized(ReadResponse.class, Builder.class);
+            }
+
+            // Construct using
+            // org.apache.flink.connector.prometheus.sink.prometheus.Remote.ReadResponse.newBuilder()
+            private Builder() {}
+
+            private Builder(BuilderParent parent) {
+                super(parent);
+            }
+
+            @Override
+            public Builder clear() {
+                super.clear();
+                bitField0_ = 0;
+                if (resultsBuilder_ == null) {
+                    results_ = java.util.Collections.emptyList();
+                } else {
+                    results_ = null;
+                    resultsBuilder_.clear();
+                }
+                bitField0_ = (bitField0_ & ~0x00000001);
+                return this;
+            }
+
+            @Override
+            public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() {
+                return internal_static_prometheus_ReadResponse_descriptor;
+            }
+
+            @Override
+            public ReadResponse getDefaultInstanceForType() {
+                return getDefaultInstance();
+            }
+
+            @Override
+            public ReadResponse build() {
+                ReadResponse result = buildPartial();
+                if (!result.isInitialized()) {
+                    throw newUninitializedMessageException(result);
+                }
+                return result;
+            }
+
+            @Override
+            public ReadResponse buildPartial() {
+                ReadResponse result = new ReadResponse(this);
+                buildPartialRepeatedFields(result);
+                if (bitField0_ != 0) {
+                    buildPartial0(result);
+                }
+                onBuilt();
+                return result;
+            }
+
+            private void buildPartialRepeatedFields(ReadResponse result) {
+                if (resultsBuilder_ == null) {
+                    if (((bitField0_ & 0x00000001) != 0)) {
+                        results_ = java.util.Collections.unmodifiableList(results_);
+                        bitField0_ = (bitField0_ & ~0x00000001);
+                    }
+                    result.results_ = results_;
+                } else {
+                    result.results_ = resultsBuilder_.build();
+                }
+            }
+
+            private void buildPartial0(ReadResponse result) {
+                int from_bitField0_ = bitField0_;
+            }
+
+            @Override
+            public Builder mergeFrom(com.google.protobuf.Message other) {
+                if (other instanceof ReadResponse) {
+                    return mergeFrom((ReadResponse) other);
+                } else {
+                    super.mergeFrom(other);
+                    return this;
+                }
+            }
+
+            public Builder mergeFrom(ReadResponse other) {
+                if (other == getDefaultInstance()) return this;
+                if (resultsBuilder_ == null) {
+                    if (!other.results_.isEmpty()) {
+                        if (results_.isEmpty()) {
+                            results_ = other.results_;
+                            bitField0_ = (bitField0_ & ~0x00000001);
+                        } else {
+                            ensureResultsIsMutable();
+                            results_.addAll(other.results_);
+                        }
+                        onChanged();
+                    }
+                } else {
+                    if (!other.results_.isEmpty()) {
+                        if (resultsBuilder_.isEmpty()) {
+                            resultsBuilder_.dispose();
+                            resultsBuilder_ = null;
+                            results_ = other.results_;
+                            bitField0_ = (bitField0_ & ~0x00000001);
+                            resultsBuilder_ =
+                                    com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders
+                                            ? getResultsFieldBuilder()
+                                            : null;
+                        } else {
+                            resultsBuilder_.addAllMessages(other.results_);
+                        }
+                    }
+                }
+                this.mergeUnknownFields(other.getUnknownFields());
+                onChanged();
+                return this;
+            }
+
+            @Override
+            public final boolean isInitialized() {
+                return true;
+            }
+
+            @Override
+            public Builder mergeFrom(
+                    com.google.protobuf.CodedInputStream input,
+                    com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                    throws java.io.IOException {
+                if (extensionRegistry == null) {
+                    throw new NullPointerException();
+                }
+                try {
+                    boolean done = false;
+                    while (!done) {
+                        int tag = input.readTag();
+                        switch (tag) {
+                            case 0:
+                                done = true;
+                                break;
+                            case 10:
+                                {
+                                    QueryResult m =
+                                            input.readMessage(
+                                                    QueryResult.parser(), extensionRegistry);
+                                    if (resultsBuilder_ == null) {
+                                        ensureResultsIsMutable();
+                                        results_.add(m);
+                                    } else {
+                                        resultsBuilder_.addMessage(m);
+                                    }
+                                    break;
+                                } // case 10
+                            default:
+                                {
+                                    if (!super.parseUnknownField(input, extensionRegistry, tag)) {
+                                        done = true; // was an endgroup tag
+                                    }
+                                    break;
+                                } // default:
+                        } // switch (tag)
+                    } // while (!done)
+                } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+                    throw e.unwrapIOException();
+                } finally {
+                    onChanged();
+                } // finally
+                return this;
+            }
+
+            private int bitField0_;
+
+            private java.util.List<QueryResult> results_ = java.util.Collections.emptyList();
+
+            private void ensureResultsIsMutable() {
+                if (!((bitField0_ & 0x00000001) != 0)) {
+                    results_ = new java.util.ArrayList<QueryResult>(results_);
+                    bitField0_ |= 0x00000001;
+                }
+            }
+
+            private com.google.protobuf.RepeatedFieldBuilderV3<
+                            QueryResult, QueryResult.Builder, QueryResultOrBuilder>
+                    resultsBuilder_;
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public java.util.List<QueryResult> getResultsList() {
+                if (resultsBuilder_ == null) {
+                    return java.util.Collections.unmodifiableList(results_);
+                } else {
+                    return resultsBuilder_.getMessageList();
+                }
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public int getResultsCount() {
+                if (resultsBuilder_ == null) {
+                    return results_.size();
+                } else {
+                    return resultsBuilder_.getCount();
+                }
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public QueryResult getResults(int index) {
+                if (resultsBuilder_ == null) {
+                    return results_.get(index);
+                } else {
+                    return resultsBuilder_.getMessage(index);
+                }
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public Builder setResults(int index, QueryResult value) {
+                if (resultsBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureResultsIsMutable();
+                    results_.set(index, value);
+                    onChanged();
+                } else {
+                    resultsBuilder_.setMessage(index, value);
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public Builder setResults(int index, QueryResult.Builder builderForValue) {
+                if (resultsBuilder_ == null) {
+                    ensureResultsIsMutable();
+                    results_.set(index, builderForValue.build());
+                    onChanged();
+                } else {
+                    resultsBuilder_.setMessage(index, builderForValue.build());
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public Builder addResults(QueryResult value) {
+                if (resultsBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureResultsIsMutable();
+                    results_.add(value);
+                    onChanged();
+                } else {
+                    resultsBuilder_.addMessage(value);
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public Builder addResults(int index, QueryResult value) {
+                if (resultsBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureResultsIsMutable();
+                    results_.add(index, value);
+                    onChanged();
+                } else {
+                    resultsBuilder_.addMessage(index, value);
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public Builder addResults(QueryResult.Builder builderForValue) {
+                if (resultsBuilder_ == null) {
+                    ensureResultsIsMutable();
+                    results_.add(builderForValue.build());
+                    onChanged();
+                } else {
+                    resultsBuilder_.addMessage(builderForValue.build());
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public Builder addResults(int index, QueryResult.Builder builderForValue) {
+                if (resultsBuilder_ == null) {
+                    ensureResultsIsMutable();
+                    results_.add(index, builderForValue.build());
+                    onChanged();
+                } else {
+                    resultsBuilder_.addMessage(index, builderForValue.build());
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public Builder addAllResults(Iterable<? extends QueryResult> values) {
+                if (resultsBuilder_ == null) {
+                    ensureResultsIsMutable();
+                    com.google.protobuf.AbstractMessageLite.Builder.addAll(values, results_);
+                    onChanged();
+                } else {
+                    resultsBuilder_.addAllMessages(values);
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public Builder clearResults() {
+                if (resultsBuilder_ == null) {
+                    results_ = java.util.Collections.emptyList();
+                    bitField0_ = (bitField0_ & ~0x00000001);
+                    onChanged();
+                } else {
+                    resultsBuilder_.clear();
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public Builder removeResults(int index) {
+                if (resultsBuilder_ == null) {
+                    ensureResultsIsMutable();
+                    results_.remove(index);
+                    onChanged();
+                } else {
+                    resultsBuilder_.remove(index);
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public QueryResult.Builder getResultsBuilder(int index) {
+                return getResultsFieldBuilder().getBuilder(index);
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public QueryResultOrBuilder getResultsOrBuilder(int index) {
+                if (resultsBuilder_ == null) {
+                    return results_.get(index);
+                } else {
+                    return resultsBuilder_.getMessageOrBuilder(index);
+                }
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public java.util.List<? extends QueryResultOrBuilder> getResultsOrBuilderList() {
+                if (resultsBuilder_ != null) {
+                    return resultsBuilder_.getMessageOrBuilderList();
+                } else {
+                    return java.util.Collections.unmodifiableList(results_);
+                }
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public QueryResult.Builder addResultsBuilder() {
+                return getResultsFieldBuilder().addBuilder(QueryResult.getDefaultInstance());
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public QueryResult.Builder addResultsBuilder(int index) {
+                return getResultsFieldBuilder().addBuilder(index, QueryResult.getDefaultInstance());
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * In same order as the request's queries.
+             * </pre>
+             *
+             * <code>repeated .prometheus.QueryResult results = 1;</code>
+             */
+            public java.util.List<QueryResult.Builder> getResultsBuilderList() {
+                return getResultsFieldBuilder().getBuilderList();
+            }
+
+            private com.google.protobuf.RepeatedFieldBuilderV3<
+                            QueryResult, QueryResult.Builder, QueryResultOrBuilder>
+                    getResultsFieldBuilder() {
+                if (resultsBuilder_ == null) {
+                    resultsBuilder_ =
+                            new com.google.protobuf.RepeatedFieldBuilderV3<
+                                    QueryResult, QueryResult.Builder, QueryResultOrBuilder>(
+                                    results_,
+                                    ((bitField0_ & 0x00000001) != 0),
+                                    getParentForChildren(),
+                                    isClean());
+                    results_ = null;
+                }
+                return resultsBuilder_;
+            }
+
+            @Override
+            public final Builder setUnknownFields(
+                    final com.google.protobuf.UnknownFieldSet unknownFields) {
+                return super.setUnknownFields(unknownFields);
+            }
+
+            @Override
+            public final Builder mergeUnknownFields(
+                    final com.google.protobuf.UnknownFieldSet unknownFields) {
+                return super.mergeUnknownFields(unknownFields);
+            }
+
+            // @@protoc_insertion_point(builder_scope:prometheus.ReadResponse)
+        }
+
+        // @@protoc_insertion_point(class_scope:prometheus.ReadResponse)
+        private static final ReadResponse DEFAULT_INSTANCE;
+
+        static {
+            DEFAULT_INSTANCE = new ReadResponse();
+        }
+
+        public static ReadResponse getDefaultInstance() {
+            return DEFAULT_INSTANCE;
+        }
+
+        private static final com.google.protobuf.Parser<ReadResponse> PARSER =
+                new com.google.protobuf.AbstractParser<ReadResponse>() {
+                    @Override
+                    public ReadResponse parsePartialFrom(
+                            com.google.protobuf.CodedInputStream input,
+                            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                            throws com.google.protobuf.InvalidProtocolBufferException {
+                        Builder builder = newBuilder();
+                        try {
+                            builder.mergeFrom(input, extensionRegistry);
+                        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+                            throw e.setUnfinishedMessage(builder.buildPartial());
+                        } catch (com.google.protobuf.UninitializedMessageException e) {
+                            throw e.asInvalidProtocolBufferException()
+                                    .setUnfinishedMessage(builder.buildPartial());
+                        } catch (java.io.IOException e) {
+                            throw new com.google.protobuf.InvalidProtocolBufferException(e)
+                                    .setUnfinishedMessage(builder.buildPartial());
+                        }
+                        return builder.buildPartial();
+                    }
+                };
+
+        public static com.google.protobuf.Parser<ReadResponse> parser() {
+            return PARSER;
+        }
+
+        @Override
+        public com.google.protobuf.Parser<ReadResponse> getParserForType() {
+            return PARSER;
+        }
+
+        @Override
+        public ReadResponse getDefaultInstanceForType() {
+            return DEFAULT_INSTANCE;
+        }
+    }
+
+    public interface QueryOrBuilder
+            extends
+            // @@protoc_insertion_point(interface_extends:prometheus.Query)
+            com.google.protobuf.MessageOrBuilder {
+
+        /**
+         * <code>int64 start_timestamp_ms = 1;</code>
+         *
+         * @return The startTimestampMs.
+         */
+        long getStartTimestampMs();
+
+        /**
+         * <code>int64 end_timestamp_ms = 2;</code>
+         *
+         * @return The endTimestampMs.
+         */
+        long getEndTimestampMs();
+
+        /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+        java.util.List<Types.LabelMatcher> getMatchersList();
+
+        /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+        Types.LabelMatcher getMatchers(int index);
+
+        /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+        int getMatchersCount();
+
+        /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+        java.util.List<? extends Types.LabelMatcherOrBuilder> getMatchersOrBuilderList();
+
+        /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+        Types.LabelMatcherOrBuilder getMatchersOrBuilder(int index);
+
+        /**
+         * <code>.prometheus.ReadHints hints = 4;</code>
+         *
+         * @return Whether the hints field is set.
+         */
+        boolean hasHints();
+
+        /**
+         * <code>.prometheus.ReadHints hints = 4;</code>
+         *
+         * @return The hints.
+         */
+        Types.ReadHints getHints();
+
+        /** <code>.prometheus.ReadHints hints = 4;</code> */
+        Types.ReadHintsOrBuilder getHintsOrBuilder();
+    }
+
+    /** Protobuf type {@code prometheus.Query} */
+    public static final class Query extends com.google.protobuf.GeneratedMessageV3
+            implements
+            // @@protoc_insertion_point(message_implements:prometheus.Query)
+            QueryOrBuilder {
+        private static final long serialVersionUID = 0L;
+
+        // Use Query.newBuilder() to construct.
+        private Query(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+            super(builder);
+        }
+
+        private Query() {
+            matchers_ = java.util.Collections.emptyList();
+        }
+
+        @Override
+        @SuppressWarnings({"unused"})
+        protected Object newInstance(UnusedPrivateParameter unused) {
+            return new Query();
+        }
+
+        public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
+            return internal_static_prometheus_Query_descriptor;
+        }
+
+        @Override
+        protected FieldAccessorTable internalGetFieldAccessorTable() {
+            return internal_static_prometheus_Query_fieldAccessorTable
+                    .ensureFieldAccessorsInitialized(Query.class, Builder.class);
+        }
+
+        public static final int START_TIMESTAMP_MS_FIELD_NUMBER = 1;
+        private long startTimestampMs_ = 0L;
+
+        /**
+         * <code>int64 start_timestamp_ms = 1;</code>
+         *
+         * @return The startTimestampMs.
+         */
+        @Override
+        public long getStartTimestampMs() {
+            return startTimestampMs_;
+        }
+
+        public static final int END_TIMESTAMP_MS_FIELD_NUMBER = 2;
+        private long endTimestampMs_ = 0L;
+
+        /**
+         * <code>int64 end_timestamp_ms = 2;</code>
+         *
+         * @return The endTimestampMs.
+         */
+        @Override
+        public long getEndTimestampMs() {
+            return endTimestampMs_;
+        }
+
+        public static final int MATCHERS_FIELD_NUMBER = 3;
+
+        @SuppressWarnings("serial")
+        private java.util.List<Types.LabelMatcher> matchers_;
+
+        /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+        @Override
+        public java.util.List<Types.LabelMatcher> getMatchersList() {
+            return matchers_;
+        }
+
+        /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+        @Override
+        public java.util.List<? extends Types.LabelMatcherOrBuilder> getMatchersOrBuilderList() {
+            return matchers_;
+        }
+
+        /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+        @Override
+        public int getMatchersCount() {
+            return matchers_.size();
+        }
+
+        /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+        @Override
+        public Types.LabelMatcher getMatchers(int index) {
+            return matchers_.get(index);
+        }
+
+        /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+        @Override
+        public Types.LabelMatcherOrBuilder getMatchersOrBuilder(int index) {
+            return matchers_.get(index);
+        }
+
+        public static final int HINTS_FIELD_NUMBER = 4;
+        private Types.ReadHints hints_;
+
+        /**
+         * <code>.prometheus.ReadHints hints = 4;</code>
+         *
+         * @return Whether the hints field is set.
+         */
+        @Override
+        public boolean hasHints() {
+            return hints_ != null;
+        }
+
+        /**
+         * <code>.prometheus.ReadHints hints = 4;</code>
+         *
+         * @return The hints.
+         */
+        @Override
+        public Types.ReadHints getHints() {
+            return hints_ == null ? Types.ReadHints.getDefaultInstance() : hints_;
+        }
+
+        /** <code>.prometheus.ReadHints hints = 4;</code> */
+        @Override
+        public Types.ReadHintsOrBuilder getHintsOrBuilder() {
+            return hints_ == null ? Types.ReadHints.getDefaultInstance() : hints_;
+        }
+
+        private byte memoizedIsInitialized = -1;
+
+        @Override
+        public final boolean isInitialized() {
+            byte isInitialized = memoizedIsInitialized;
+            if (isInitialized == 1) return true;
+            if (isInitialized == 0) return false;
+
+            memoizedIsInitialized = 1;
+            return true;
+        }
+
+        @Override
+        public void writeTo(com.google.protobuf.CodedOutputStream output)
+                throws java.io.IOException {
+            if (startTimestampMs_ != 0L) {
+                output.writeInt64(1, startTimestampMs_);
+            }
+            if (endTimestampMs_ != 0L) {
+                output.writeInt64(2, endTimestampMs_);
+            }
+            for (int i = 0; i < matchers_.size(); i++) {
+                output.writeMessage(3, matchers_.get(i));
+            }
+            if (hints_ != null) {
+                output.writeMessage(4, getHints());
+            }
+            getUnknownFields().writeTo(output);
+        }
+
+        @Override
+        public int getSerializedSize() {
+            int size = memoizedSize;
+            if (size != -1) return size;
+
+            size = 0;
+            if (startTimestampMs_ != 0L) {
+                size +=
+                        com.google.protobuf.CodedOutputStream.computeInt64Size(
+                                1, startTimestampMs_);
+            }
+            if (endTimestampMs_ != 0L) {
+                size += com.google.protobuf.CodedOutputStream.computeInt64Size(2, endTimestampMs_);
+            }
+            for (int i = 0; i < matchers_.size(); i++) {
+                size +=
+                        com.google.protobuf.CodedOutputStream.computeMessageSize(
+                                3, matchers_.get(i));
+            }
+            if (hints_ != null) {
+                size += com.google.protobuf.CodedOutputStream.computeMessageSize(4, getHints());
+            }
+            size += getUnknownFields().getSerializedSize();
+            memoizedSize = size;
+            return size;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof Query)) {
+                return super.equals(obj);
+            }
+            Query other = (Query) obj;
+
+            if (getStartTimestampMs() != other.getStartTimestampMs()) return false;
+            if (getEndTimestampMs() != other.getEndTimestampMs()) return false;
+            if (!getMatchersList().equals(other.getMatchersList())) return false;
+            if (hasHints() != other.hasHints()) return false;
+            if (hasHints()) {
+                if (!getHints().equals(other.getHints())) return false;
+            }
+            if (!getUnknownFields().equals(other.getUnknownFields())) return false;
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            if (memoizedHashCode != 0) {
+                return memoizedHashCode;
+            }
+            int hash = 41;
+            hash = (19 * hash) + getDescriptor().hashCode();
+            hash = (37 * hash) + START_TIMESTAMP_MS_FIELD_NUMBER;
+            hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getStartTimestampMs());
+            hash = (37 * hash) + END_TIMESTAMP_MS_FIELD_NUMBER;
+            hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getEndTimestampMs());
+            if (getMatchersCount() > 0) {
+                hash = (37 * hash) + MATCHERS_FIELD_NUMBER;
+                hash = (53 * hash) + getMatchersList().hashCode();
+            }
+            if (hasHints()) {
+                hash = (37 * hash) + HINTS_FIELD_NUMBER;
+                hash = (53 * hash) + getHints().hashCode();
+            }
+            hash = (29 * hash) + getUnknownFields().hashCode();
+            memoizedHashCode = hash;
+            return hash;
+        }
+
+        public static Query parseFrom(java.nio.ByteBuffer data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static Query parseFrom(
+                java.nio.ByteBuffer data,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static Query parseFrom(com.google.protobuf.ByteString data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static Query parseFrom(
+                com.google.protobuf.ByteString data,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static Query parseFrom(byte[] data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static Query parseFrom(
+                byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static Query parseFrom(java.io.InputStream input) throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
+        }
+
+        public static Query parseFrom(
+                java.io.InputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        public static Query parseDelimitedFrom(java.io.InputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(
+                    PARSER, input);
+        }
+
+        public static Query parseDelimitedFrom(
+                java.io.InputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        public static Query parseFrom(com.google.protobuf.CodedInputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
+        }
+
+        public static Query parseFrom(
+                com.google.protobuf.CodedInputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        @Override
+        public Builder newBuilderForType() {
+            return newBuilder();
+        }
+
+        public static Builder newBuilder() {
+            return DEFAULT_INSTANCE.toBuilder();
+        }
+
+        public static Builder newBuilder(Query prototype) {
+            return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+        }
+
+        @Override
+        public Builder toBuilder() {
+            return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this);
+        }
+
+        @Override
+        protected Builder newBuilderForType(BuilderParent parent) {
+            Builder builder = new Builder(parent);
+            return builder;
+        }
+
+        /** Protobuf type {@code prometheus.Query} */
+        public static final class Builder
+                extends com.google.protobuf.GeneratedMessageV3.Builder<Builder>
+                implements
+                // @@protoc_insertion_point(builder_implements:prometheus.Query)
+                QueryOrBuilder {
+            public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
+                return internal_static_prometheus_Query_descriptor;
+            }
+
+            @Override
+            protected FieldAccessorTable internalGetFieldAccessorTable() {
+                return internal_static_prometheus_Query_fieldAccessorTable
+                        .ensureFieldAccessorsInitialized(Query.class, Builder.class);
+            }
+
+            // Construct using
+            // org.apache.flink.connector.prometheus.sink.prometheus.Remote.Query.newBuilder()
+            private Builder() {}
+
+            private Builder(BuilderParent parent) {
+                super(parent);
+            }
+
+            @Override
+            public Builder clear() {
+                super.clear();
+                bitField0_ = 0;
+                startTimestampMs_ = 0L;
+                endTimestampMs_ = 0L;
+                if (matchersBuilder_ == null) {
+                    matchers_ = java.util.Collections.emptyList();
+                } else {
+                    matchers_ = null;
+                    matchersBuilder_.clear();
+                }
+                bitField0_ = (bitField0_ & ~0x00000004);
+                hints_ = null;
+                if (hintsBuilder_ != null) {
+                    hintsBuilder_.dispose();
+                    hintsBuilder_ = null;
+                }
+                return this;
+            }
+
+            @Override
+            public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() {
+                return internal_static_prometheus_Query_descriptor;
+            }
+
+            @Override
+            public Query getDefaultInstanceForType() {
+                return getDefaultInstance();
+            }
+
+            @Override
+            public Query build() {
+                Query result = buildPartial();
+                if (!result.isInitialized()) {
+                    throw newUninitializedMessageException(result);
+                }
+                return result;
+            }
+
+            @Override
+            public Query buildPartial() {
+                Query result = new Query(this);
+                buildPartialRepeatedFields(result);
+                if (bitField0_ != 0) {
+                    buildPartial0(result);
+                }
+                onBuilt();
+                return result;
+            }
+
+            private void buildPartialRepeatedFields(Query result) {
+                if (matchersBuilder_ == null) {
+                    if (((bitField0_ & 0x00000004) != 0)) {
+                        matchers_ = java.util.Collections.unmodifiableList(matchers_);
+                        bitField0_ = (bitField0_ & ~0x00000004);
+                    }
+                    result.matchers_ = matchers_;
+                } else {
+                    result.matchers_ = matchersBuilder_.build();
+                }
+            }
+
+            private void buildPartial0(Query result) {
+                int from_bitField0_ = bitField0_;
+                if (((from_bitField0_ & 0x00000001) != 0)) {
+                    result.startTimestampMs_ = startTimestampMs_;
+                }
+                if (((from_bitField0_ & 0x00000002) != 0)) {
+                    result.endTimestampMs_ = endTimestampMs_;
+                }
+                if (((from_bitField0_ & 0x00000008) != 0)) {
+                    result.hints_ = hintsBuilder_ == null ? hints_ : hintsBuilder_.build();
+                }
+            }
+
+            @Override
+            public Builder mergeFrom(com.google.protobuf.Message other) {
+                if (other instanceof Query) {
+                    return mergeFrom((Query) other);
+                } else {
+                    super.mergeFrom(other);
+                    return this;
+                }
+            }
+
+            public Builder mergeFrom(Query other) {
+                if (other == getDefaultInstance()) return this;
+                if (other.getStartTimestampMs() != 0L) {
+                    setStartTimestampMs(other.getStartTimestampMs());
+                }
+                if (other.getEndTimestampMs() != 0L) {
+                    setEndTimestampMs(other.getEndTimestampMs());
+                }
+                if (matchersBuilder_ == null) {
+                    if (!other.matchers_.isEmpty()) {
+                        if (matchers_.isEmpty()) {
+                            matchers_ = other.matchers_;
+                            bitField0_ = (bitField0_ & ~0x00000004);
+                        } else {
+                            ensureMatchersIsMutable();
+                            matchers_.addAll(other.matchers_);
+                        }
+                        onChanged();
+                    }
+                } else {
+                    if (!other.matchers_.isEmpty()) {
+                        if (matchersBuilder_.isEmpty()) {
+                            matchersBuilder_.dispose();
+                            matchersBuilder_ = null;
+                            matchers_ = other.matchers_;
+                            bitField0_ = (bitField0_ & ~0x00000004);
+                            matchersBuilder_ =
+                                    com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders
+                                            ? getMatchersFieldBuilder()
+                                            : null;
+                        } else {
+                            matchersBuilder_.addAllMessages(other.matchers_);
+                        }
+                    }
+                }
+                if (other.hasHints()) {
+                    mergeHints(other.getHints());
+                }
+                this.mergeUnknownFields(other.getUnknownFields());
+                onChanged();
+                return this;
+            }
+
+            @Override
+            public final boolean isInitialized() {
+                return true;
+            }
+
+            @Override
+            public Builder mergeFrom(
+                    com.google.protobuf.CodedInputStream input,
+                    com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                    throws java.io.IOException {
+                if (extensionRegistry == null) {
+                    throw new NullPointerException();
+                }
+                try {
+                    boolean done = false;
+                    while (!done) {
+                        int tag = input.readTag();
+                        switch (tag) {
+                            case 0:
+                                done = true;
+                                break;
+                            case 8:
+                                {
+                                    startTimestampMs_ = input.readInt64();
+                                    bitField0_ |= 0x00000001;
+                                    break;
+                                } // case 8
+                            case 16:
+                                {
+                                    endTimestampMs_ = input.readInt64();
+                                    bitField0_ |= 0x00000002;
+                                    break;
+                                } // case 16
+                            case 26:
+                                {
+                                    Types.LabelMatcher m =
+                                            input.readMessage(
+                                                    Types.LabelMatcher.parser(), extensionRegistry);
+                                    if (matchersBuilder_ == null) {
+                                        ensureMatchersIsMutable();
+                                        matchers_.add(m);
+                                    } else {
+                                        matchersBuilder_.addMessage(m);
+                                    }
+                                    break;
+                                } // case 26
+                            case 34:
+                                {
+                                    input.readMessage(
+                                            getHintsFieldBuilder().getBuilder(), extensionRegistry);
+                                    bitField0_ |= 0x00000008;
+                                    break;
+                                } // case 34
+                            default:
+                                {
+                                    if (!super.parseUnknownField(input, extensionRegistry, tag)) {
+                                        done = true; // was an endgroup tag
+                                    }
+                                    break;
+                                } // default:
+                        } // switch (tag)
+                    } // while (!done)
+                } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+                    throw e.unwrapIOException();
+                } finally {
+                    onChanged();
+                } // finally
+                return this;
+            }
+
+            private int bitField0_;
+
+            private long startTimestampMs_;
+
+            /**
+             * <code>int64 start_timestamp_ms = 1;</code>
+             *
+             * @return The startTimestampMs.
+             */
+            @Override
+            public long getStartTimestampMs() {
+                return startTimestampMs_;
+            }
+
+            /**
+             * <code>int64 start_timestamp_ms = 1;</code>
+             *
+             * @param value The startTimestampMs to set.
+             * @return This builder for chaining.
+             */
+            public Builder setStartTimestampMs(long value) {
+
+                startTimestampMs_ = value;
+                bitField0_ |= 0x00000001;
+                onChanged();
+                return this;
+            }
+
+            /**
+             * <code>int64 start_timestamp_ms = 1;</code>
+             *
+             * @return This builder for chaining.
+             */
+            public Builder clearStartTimestampMs() {
+                bitField0_ = (bitField0_ & ~0x00000001);
+                startTimestampMs_ = 0L;
+                onChanged();
+                return this;
+            }
+
+            private long endTimestampMs_;
+
+            /**
+             * <code>int64 end_timestamp_ms = 2;</code>
+             *
+             * @return The endTimestampMs.
+             */
+            @Override
+            public long getEndTimestampMs() {
+                return endTimestampMs_;
+            }
+
+            /**
+             * <code>int64 end_timestamp_ms = 2;</code>
+             *
+             * @param value The endTimestampMs to set.
+             * @return This builder for chaining.
+             */
+            public Builder setEndTimestampMs(long value) {
+
+                endTimestampMs_ = value;
+                bitField0_ |= 0x00000002;
+                onChanged();
+                return this;
+            }
+
+            /**
+             * <code>int64 end_timestamp_ms = 2;</code>
+             *
+             * @return This builder for chaining.
+             */
+            public Builder clearEndTimestampMs() {
+                bitField0_ = (bitField0_ & ~0x00000002);
+                endTimestampMs_ = 0L;
+                onChanged();
+                return this;
+            }
+
+            private java.util.List<Types.LabelMatcher> matchers_ =
+                    java.util.Collections.emptyList();
+
+            private void ensureMatchersIsMutable() {
+                if (!((bitField0_ & 0x00000004) != 0)) {
+                    matchers_ = new java.util.ArrayList<Types.LabelMatcher>(matchers_);
+                    bitField0_ |= 0x00000004;
+                }
+            }
+
+            private com.google.protobuf.RepeatedFieldBuilderV3<
+                            Types.LabelMatcher,
+                            Types.LabelMatcher.Builder,
+                            Types.LabelMatcherOrBuilder>
+                    matchersBuilder_;
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public java.util.List<Types.LabelMatcher> getMatchersList() {
+                if (matchersBuilder_ == null) {
+                    return java.util.Collections.unmodifiableList(matchers_);
+                } else {
+                    return matchersBuilder_.getMessageList();
+                }
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public int getMatchersCount() {
+                if (matchersBuilder_ == null) {
+                    return matchers_.size();
+                } else {
+                    return matchersBuilder_.getCount();
+                }
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Types.LabelMatcher getMatchers(int index) {
+                if (matchersBuilder_ == null) {
+                    return matchers_.get(index);
+                } else {
+                    return matchersBuilder_.getMessage(index);
+                }
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Builder setMatchers(int index, Types.LabelMatcher value) {
+                if (matchersBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureMatchersIsMutable();
+                    matchers_.set(index, value);
+                    onChanged();
+                } else {
+                    matchersBuilder_.setMessage(index, value);
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Builder setMatchers(int index, Types.LabelMatcher.Builder builderForValue) {
+                if (matchersBuilder_ == null) {
+                    ensureMatchersIsMutable();
+                    matchers_.set(index, builderForValue.build());
+                    onChanged();
+                } else {
+                    matchersBuilder_.setMessage(index, builderForValue.build());
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Builder addMatchers(Types.LabelMatcher value) {
+                if (matchersBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureMatchersIsMutable();
+                    matchers_.add(value);
+                    onChanged();
+                } else {
+                    matchersBuilder_.addMessage(value);
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Builder addMatchers(int index, Types.LabelMatcher value) {
+                if (matchersBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureMatchersIsMutable();
+                    matchers_.add(index, value);
+                    onChanged();
+                } else {
+                    matchersBuilder_.addMessage(index, value);
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Builder addMatchers(Types.LabelMatcher.Builder builderForValue) {
+                if (matchersBuilder_ == null) {
+                    ensureMatchersIsMutable();
+                    matchers_.add(builderForValue.build());
+                    onChanged();
+                } else {
+                    matchersBuilder_.addMessage(builderForValue.build());
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Builder addMatchers(int index, Types.LabelMatcher.Builder builderForValue) {
+                if (matchersBuilder_ == null) {
+                    ensureMatchersIsMutable();
+                    matchers_.add(index, builderForValue.build());
+                    onChanged();
+                } else {
+                    matchersBuilder_.addMessage(index, builderForValue.build());
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Builder addAllMatchers(Iterable<? extends Types.LabelMatcher> values) {
+                if (matchersBuilder_ == null) {
+                    ensureMatchersIsMutable();
+                    com.google.protobuf.AbstractMessageLite.Builder.addAll(values, matchers_);
+                    onChanged();
+                } else {
+                    matchersBuilder_.addAllMessages(values);
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Builder clearMatchers() {
+                if (matchersBuilder_ == null) {
+                    matchers_ = java.util.Collections.emptyList();
+                    bitField0_ = (bitField0_ & ~0x00000004);
+                    onChanged();
+                } else {
+                    matchersBuilder_.clear();
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Builder removeMatchers(int index) {
+                if (matchersBuilder_ == null) {
+                    ensureMatchersIsMutable();
+                    matchers_.remove(index);
+                    onChanged();
+                } else {
+                    matchersBuilder_.remove(index);
+                }
+                return this;
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Types.LabelMatcher.Builder getMatchersBuilder(int index) {
+                return getMatchersFieldBuilder().getBuilder(index);
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Types.LabelMatcherOrBuilder getMatchersOrBuilder(int index) {
+                if (matchersBuilder_ == null) {
+                    return matchers_.get(index);
+                } else {
+                    return matchersBuilder_.getMessageOrBuilder(index);
+                }
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public java.util.List<? extends Types.LabelMatcherOrBuilder>
+                    getMatchersOrBuilderList() {
+                if (matchersBuilder_ != null) {
+                    return matchersBuilder_.getMessageOrBuilderList();
+                } else {
+                    return java.util.Collections.unmodifiableList(matchers_);
+                }
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Types.LabelMatcher.Builder addMatchersBuilder() {
+                return getMatchersFieldBuilder()
+                        .addBuilder(Types.LabelMatcher.getDefaultInstance());
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public Types.LabelMatcher.Builder addMatchersBuilder(int index) {
+                return getMatchersFieldBuilder()
+                        .addBuilder(index, Types.LabelMatcher.getDefaultInstance());
+            }
+
+            /** <code>repeated .prometheus.LabelMatcher matchers = 3;</code> */
+            public java.util.List<Types.LabelMatcher.Builder> getMatchersBuilderList() {
+                return getMatchersFieldBuilder().getBuilderList();
+            }
+
+            private com.google.protobuf.RepeatedFieldBuilderV3<
+                            Types.LabelMatcher,
+                            Types.LabelMatcher.Builder,
+                            Types.LabelMatcherOrBuilder>
+                    getMatchersFieldBuilder() {
+                if (matchersBuilder_ == null) {
+                    matchersBuilder_ =
+                            new com.google.protobuf.RepeatedFieldBuilderV3<
+                                    Types.LabelMatcher,
+                                    Types.LabelMatcher.Builder,
+                                    Types.LabelMatcherOrBuilder>(
+                                    matchers_,
+                                    ((bitField0_ & 0x00000004) != 0),
+                                    getParentForChildren(),
+                                    isClean());
+                    matchers_ = null;
+                }
+                return matchersBuilder_;
+            }
+
+            private Types.ReadHints hints_;
+            private com.google.protobuf.SingleFieldBuilderV3<
+                            Types.ReadHints, Types.ReadHints.Builder, Types.ReadHintsOrBuilder>
+                    hintsBuilder_;
+
+            /**
+             * <code>.prometheus.ReadHints hints = 4;</code>
+             *
+             * @return Whether the hints field is set.
+             */
+            public boolean hasHints() {
+                return ((bitField0_ & 0x00000008) != 0);
+            }
+
+            /**
+             * <code>.prometheus.ReadHints hints = 4;</code>
+             *
+             * @return The hints.
+             */
+            public Types.ReadHints getHints() {
+                if (hintsBuilder_ == null) {
+                    return hints_ == null ? Types.ReadHints.getDefaultInstance() : hints_;
+                } else {
+                    return hintsBuilder_.getMessage();
+                }
+            }
+
+            /** <code>.prometheus.ReadHints hints = 4;</code> */
+            public Builder setHints(Types.ReadHints value) {
+                if (hintsBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    hints_ = value;
+                } else {
+                    hintsBuilder_.setMessage(value);
+                }
+                bitField0_ |= 0x00000008;
+                onChanged();
+                return this;
+            }
+
+            /** <code>.prometheus.ReadHints hints = 4;</code> */
+            public Builder setHints(Types.ReadHints.Builder builderForValue) {
+                if (hintsBuilder_ == null) {
+                    hints_ = builderForValue.build();
+                } else {
+                    hintsBuilder_.setMessage(builderForValue.build());
+                }
+                bitField0_ |= 0x00000008;
+                onChanged();
+                return this;
+            }
+
+            /** <code>.prometheus.ReadHints hints = 4;</code> */
+            public Builder mergeHints(Types.ReadHints value) {
+                if (hintsBuilder_ == null) {
+                    if (((bitField0_ & 0x00000008) != 0)
+                            && hints_ != null
+                            && hints_ != Types.ReadHints.getDefaultInstance()) {
+                        getHintsBuilder().mergeFrom(value);
+                    } else {
+                        hints_ = value;
+                    }
+                } else {
+                    hintsBuilder_.mergeFrom(value);
+                }
+                bitField0_ |= 0x00000008;
+                onChanged();
+                return this;
+            }
+
+            /** <code>.prometheus.ReadHints hints = 4;</code> */
+            public Builder clearHints() {
+                bitField0_ = (bitField0_ & ~0x00000008);
+                hints_ = null;
+                if (hintsBuilder_ != null) {
+                    hintsBuilder_.dispose();
+                    hintsBuilder_ = null;
+                }
+                onChanged();
+                return this;
+            }
+
+            /** <code>.prometheus.ReadHints hints = 4;</code> */
+            public Types.ReadHints.Builder getHintsBuilder() {
+                bitField0_ |= 0x00000008;
+                onChanged();
+                return getHintsFieldBuilder().getBuilder();
+            }
+
+            /** <code>.prometheus.ReadHints hints = 4;</code> */
+            public Types.ReadHintsOrBuilder getHintsOrBuilder() {
+                if (hintsBuilder_ != null) {
+                    return hintsBuilder_.getMessageOrBuilder();
+                } else {
+                    return hints_ == null ? Types.ReadHints.getDefaultInstance() : hints_;
+                }
+            }
+
+            /** <code>.prometheus.ReadHints hints = 4;</code> */
+            private com.google.protobuf.SingleFieldBuilderV3<
+                            Types.ReadHints, Types.ReadHints.Builder, Types.ReadHintsOrBuilder>
+                    getHintsFieldBuilder() {
+                if (hintsBuilder_ == null) {
+                    hintsBuilder_ =
+                            new com.google.protobuf.SingleFieldBuilderV3<
+                                    Types.ReadHints,
+                                    Types.ReadHints.Builder,
+                                    Types.ReadHintsOrBuilder>(
+                                    getHints(), getParentForChildren(), isClean());
+                    hints_ = null;
+                }
+                return hintsBuilder_;
+            }
+
+            @Override
+            public final Builder setUnknownFields(
+                    final com.google.protobuf.UnknownFieldSet unknownFields) {
+                return super.setUnknownFields(unknownFields);
+            }
+
+            @Override
+            public final Builder mergeUnknownFields(
+                    final com.google.protobuf.UnknownFieldSet unknownFields) {
+                return super.mergeUnknownFields(unknownFields);
+            }
+
+            // @@protoc_insertion_point(builder_scope:prometheus.Query)
+        }
+
+        // @@protoc_insertion_point(class_scope:prometheus.Query)
+        private static final Query DEFAULT_INSTANCE;
+
+        static {
+            DEFAULT_INSTANCE = new Query();
+        }
+
+        public static Query getDefaultInstance() {
+            return DEFAULT_INSTANCE;
+        }
+
+        private static final com.google.protobuf.Parser<Query> PARSER =
+                new com.google.protobuf.AbstractParser<Query>() {
+                    @Override
+                    public Query parsePartialFrom(
+                            com.google.protobuf.CodedInputStream input,
+                            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                            throws com.google.protobuf.InvalidProtocolBufferException {
+                        Builder builder = newBuilder();
+                        try {
+                            builder.mergeFrom(input, extensionRegistry);
+                        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+                            throw e.setUnfinishedMessage(builder.buildPartial());
+                        } catch (com.google.protobuf.UninitializedMessageException e) {
+                            throw e.asInvalidProtocolBufferException()
+                                    .setUnfinishedMessage(builder.buildPartial());
+                        } catch (java.io.IOException e) {
+                            throw new com.google.protobuf.InvalidProtocolBufferException(e)
+                                    .setUnfinishedMessage(builder.buildPartial());
+                        }
+                        return builder.buildPartial();
+                    }
+                };
+
+        public static com.google.protobuf.Parser<Query> parser() {
+            return PARSER;
+        }
+
+        @Override
+        public com.google.protobuf.Parser<Query> getParserForType() {
+            return PARSER;
+        }
+
+        @Override
+        public Query getDefaultInstanceForType() {
+            return DEFAULT_INSTANCE;
+        }
+    }
+
+    public interface QueryResultOrBuilder
+            extends
+            // @@protoc_insertion_point(interface_extends:prometheus.QueryResult)
+            com.google.protobuf.MessageOrBuilder {
+
+        /**
+         *
+         *
+         * <pre>
+         * Samples within a time series must be ordered by time.
+         * </pre>
+         *
+         * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+         */
+        java.util.List<Types.TimeSeries> getTimeseriesList();
+
+        /**
+         *
+         *
+         * <pre>
+         * Samples within a time series must be ordered by time.
+         * </pre>
+         *
+         * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+         */
+        Types.TimeSeries getTimeseries(int index);
+
+        /**
+         *
+         *
+         * <pre>
+         * Samples within a time series must be ordered by time.
+         * </pre>
+         *
+         * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+         */
+        int getTimeseriesCount();
+
+        /**
+         *
+         *
+         * <pre>
+         * Samples within a time series must be ordered by time.
+         * </pre>
+         *
+         * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+         */
+        java.util.List<? extends Types.TimeSeriesOrBuilder> getTimeseriesOrBuilderList();
+
+        /**
+         *
+         *
+         * <pre>
+         * Samples within a time series must be ordered by time.
+         * </pre>
+         *
+         * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+         */
+        Types.TimeSeriesOrBuilder getTimeseriesOrBuilder(int index);
+    }
+
+    /** Protobuf type {@code prometheus.QueryResult} */
+    public static final class QueryResult extends com.google.protobuf.GeneratedMessageV3
+            implements
+            // @@protoc_insertion_point(message_implements:prometheus.QueryResult)
+            QueryResultOrBuilder {
+        private static final long serialVersionUID = 0L;
+
+        // Use QueryResult.newBuilder() to construct.
+        private QueryResult(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+            super(builder);
+        }
+
+        private QueryResult() {
+            timeseries_ = java.util.Collections.emptyList();
+        }
+
+        @Override
+        @SuppressWarnings({"unused"})
+        protected Object newInstance(UnusedPrivateParameter unused) {
+            return new QueryResult();
+        }
+
+        public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
+            return internal_static_prometheus_QueryResult_descriptor;
+        }
+
+        @Override
+        protected FieldAccessorTable internalGetFieldAccessorTable() {
+            return internal_static_prometheus_QueryResult_fieldAccessorTable
+                    .ensureFieldAccessorsInitialized(QueryResult.class, Builder.class);
+        }
+
+        public static final int TIMESERIES_FIELD_NUMBER = 1;
+
+        @SuppressWarnings("serial")
+        private java.util.List<Types.TimeSeries> timeseries_;
+
+        /**
+         *
+         *
+         * <pre>
+         * Samples within a time series must be ordered by time.
+         * </pre>
+         *
+         * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+         */
+        @Override
+        public java.util.List<Types.TimeSeries> getTimeseriesList() {
+            return timeseries_;
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * Samples within a time series must be ordered by time.
+         * </pre>
+         *
+         * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+         */
+        @Override
+        public java.util.List<? extends Types.TimeSeriesOrBuilder> getTimeseriesOrBuilderList() {
+            return timeseries_;
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * Samples within a time series must be ordered by time.
+         * </pre>
+         *
+         * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+         */
+        @Override
+        public int getTimeseriesCount() {
+            return timeseries_.size();
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * Samples within a time series must be ordered by time.
+         * </pre>
+         *
+         * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+         */
+        @Override
+        public Types.TimeSeries getTimeseries(int index) {
+            return timeseries_.get(index);
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * Samples within a time series must be ordered by time.
+         * </pre>
+         *
+         * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+         */
+        @Override
+        public Types.TimeSeriesOrBuilder getTimeseriesOrBuilder(int index) {
+            return timeseries_.get(index);
+        }
+
+        private byte memoizedIsInitialized = -1;
+
+        @Override
+        public final boolean isInitialized() {
+            byte isInitialized = memoizedIsInitialized;
+            if (isInitialized == 1) return true;
+            if (isInitialized == 0) return false;
+
+            memoizedIsInitialized = 1;
+            return true;
+        }
+
+        @Override
+        public void writeTo(com.google.protobuf.CodedOutputStream output)
+                throws java.io.IOException {
+            for (int i = 0; i < timeseries_.size(); i++) {
+                output.writeMessage(1, timeseries_.get(i));
+            }
+            getUnknownFields().writeTo(output);
+        }
+
+        @Override
+        public int getSerializedSize() {
+            int size = memoizedSize;
+            if (size != -1) return size;
+
+            size = 0;
+            for (int i = 0; i < timeseries_.size(); i++) {
+                size +=
+                        com.google.protobuf.CodedOutputStream.computeMessageSize(
+                                1, timeseries_.get(i));
+            }
+            size += getUnknownFields().getSerializedSize();
+            memoizedSize = size;
+            return size;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof QueryResult)) {
+                return super.equals(obj);
+            }
+            QueryResult other = (QueryResult) obj;
+
+            if (!getTimeseriesList().equals(other.getTimeseriesList())) return false;
+            if (!getUnknownFields().equals(other.getUnknownFields())) return false;
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            if (memoizedHashCode != 0) {
+                return memoizedHashCode;
+            }
+            int hash = 41;
+            hash = (19 * hash) + getDescriptor().hashCode();
+            if (getTimeseriesCount() > 0) {
+                hash = (37 * hash) + TIMESERIES_FIELD_NUMBER;
+                hash = (53 * hash) + getTimeseriesList().hashCode();
+            }
+            hash = (29 * hash) + getUnknownFields().hashCode();
+            memoizedHashCode = hash;
+            return hash;
+        }
+
+        public static QueryResult parseFrom(java.nio.ByteBuffer data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static QueryResult parseFrom(
+                java.nio.ByteBuffer data,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static QueryResult parseFrom(com.google.protobuf.ByteString data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static QueryResult parseFrom(
+                com.google.protobuf.ByteString data,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static QueryResult parseFrom(byte[] data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static QueryResult parseFrom(
+                byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static QueryResult parseFrom(java.io.InputStream input) throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
+        }
+
+        public static QueryResult parseFrom(
+                java.io.InputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        public static QueryResult parseDelimitedFrom(java.io.InputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(
+                    PARSER, input);
+        }
+
+        public static QueryResult parseDelimitedFrom(
+                java.io.InputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        public static QueryResult parseFrom(com.google.protobuf.CodedInputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
+        }
+
+        public static QueryResult parseFrom(
+                com.google.protobuf.CodedInputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        @Override
+        public Builder newBuilderForType() {
+            return newBuilder();
+        }
+
+        public static Builder newBuilder() {
+            return DEFAULT_INSTANCE.toBuilder();
+        }
+
+        public static Builder newBuilder(QueryResult prototype) {
+            return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+        }
+
+        @Override
+        public Builder toBuilder() {
+            return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this);
+        }
+
+        @Override
+        protected Builder newBuilderForType(BuilderParent parent) {
+            Builder builder = new Builder(parent);
+            return builder;
+        }
+
+        /** Protobuf type {@code prometheus.QueryResult} */
+        public static final class Builder
+                extends com.google.protobuf.GeneratedMessageV3.Builder<Builder>
+                implements
+                // @@protoc_insertion_point(builder_implements:prometheus.QueryResult)
+                QueryResultOrBuilder {
+            public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
+                return internal_static_prometheus_QueryResult_descriptor;
+            }
+
+            @Override
+            protected FieldAccessorTable internalGetFieldAccessorTable() {
+                return internal_static_prometheus_QueryResult_fieldAccessorTable
+                        .ensureFieldAccessorsInitialized(QueryResult.class, Builder.class);
+            }
+
+            // Construct using
+            // org.apache.flink.connector.prometheus.sink.prometheus.Remote.QueryResult.newBuilder()
+            private Builder() {}
+
+            private Builder(BuilderParent parent) {
+                super(parent);
+            }
+
+            @Override
+            public Builder clear() {
+                super.clear();
+                bitField0_ = 0;
+                if (timeseriesBuilder_ == null) {
+                    timeseries_ = java.util.Collections.emptyList();
+                } else {
+                    timeseries_ = null;
+                    timeseriesBuilder_.clear();
+                }
+                bitField0_ = (bitField0_ & ~0x00000001);
+                return this;
+            }
+
+            @Override
+            public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() {
+                return internal_static_prometheus_QueryResult_descriptor;
+            }
+
+            @Override
+            public QueryResult getDefaultInstanceForType() {
+                return getDefaultInstance();
+            }
+
+            @Override
+            public QueryResult build() {
+                QueryResult result = buildPartial();
+                if (!result.isInitialized()) {
+                    throw newUninitializedMessageException(result);
+                }
+                return result;
+            }
+
+            @Override
+            public QueryResult buildPartial() {
+                QueryResult result = new QueryResult(this);
+                buildPartialRepeatedFields(result);
+                if (bitField0_ != 0) {
+                    buildPartial0(result);
+                }
+                onBuilt();
+                return result;
+            }
+
+            private void buildPartialRepeatedFields(QueryResult result) {
+                if (timeseriesBuilder_ == null) {
+                    if (((bitField0_ & 0x00000001) != 0)) {
+                        timeseries_ = java.util.Collections.unmodifiableList(timeseries_);
+                        bitField0_ = (bitField0_ & ~0x00000001);
+                    }
+                    result.timeseries_ = timeseries_;
+                } else {
+                    result.timeseries_ = timeseriesBuilder_.build();
+                }
+            }
+
+            private void buildPartial0(QueryResult result) {
+                int from_bitField0_ = bitField0_;
+            }
+
+            @Override
+            public Builder mergeFrom(com.google.protobuf.Message other) {
+                if (other instanceof QueryResult) {
+                    return mergeFrom((QueryResult) other);
+                } else {
+                    super.mergeFrom(other);
+                    return this;
+                }
+            }
+
+            public Builder mergeFrom(QueryResult other) {
+                if (other == getDefaultInstance()) return this;
+                if (timeseriesBuilder_ == null) {
+                    if (!other.timeseries_.isEmpty()) {
+                        if (timeseries_.isEmpty()) {
+                            timeseries_ = other.timeseries_;
+                            bitField0_ = (bitField0_ & ~0x00000001);
+                        } else {
+                            ensureTimeseriesIsMutable();
+                            timeseries_.addAll(other.timeseries_);
+                        }
+                        onChanged();
+                    }
+                } else {
+                    if (!other.timeseries_.isEmpty()) {
+                        if (timeseriesBuilder_.isEmpty()) {
+                            timeseriesBuilder_.dispose();
+                            timeseriesBuilder_ = null;
+                            timeseries_ = other.timeseries_;
+                            bitField0_ = (bitField0_ & ~0x00000001);
+                            timeseriesBuilder_ =
+                                    com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders
+                                            ? getTimeseriesFieldBuilder()
+                                            : null;
+                        } else {
+                            timeseriesBuilder_.addAllMessages(other.timeseries_);
+                        }
+                    }
+                }
+                this.mergeUnknownFields(other.getUnknownFields());
+                onChanged();
+                return this;
+            }
+
+            @Override
+            public final boolean isInitialized() {
+                return true;
+            }
+
+            @Override
+            public Builder mergeFrom(
+                    com.google.protobuf.CodedInputStream input,
+                    com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                    throws java.io.IOException {
+                if (extensionRegistry == null) {
+                    throw new NullPointerException();
+                }
+                try {
+                    boolean done = false;
+                    while (!done) {
+                        int tag = input.readTag();
+                        switch (tag) {
+                            case 0:
+                                done = true;
+                                break;
+                            case 10:
+                                {
+                                    Types.TimeSeries m =
+                                            input.readMessage(
+                                                    Types.TimeSeries.parser(), extensionRegistry);
+                                    if (timeseriesBuilder_ == null) {
+                                        ensureTimeseriesIsMutable();
+                                        timeseries_.add(m);
+                                    } else {
+                                        timeseriesBuilder_.addMessage(m);
+                                    }
+                                    break;
+                                } // case 10
+                            default:
+                                {
+                                    if (!super.parseUnknownField(input, extensionRegistry, tag)) {
+                                        done = true; // was an endgroup tag
+                                    }
+                                    break;
+                                } // default:
+                        } // switch (tag)
+                    } // while (!done)
+                } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+                    throw e.unwrapIOException();
+                } finally {
+                    onChanged();
+                } // finally
+                return this;
+            }
+
+            private int bitField0_;
+
+            private java.util.List<Types.TimeSeries> timeseries_ =
+                    java.util.Collections.emptyList();
+
+            private void ensureTimeseriesIsMutable() {
+                if (!((bitField0_ & 0x00000001) != 0)) {
+                    timeseries_ = new java.util.ArrayList<Types.TimeSeries>(timeseries_);
+                    bitField0_ |= 0x00000001;
+                }
+            }
+
+            private com.google.protobuf.RepeatedFieldBuilderV3<
+                            Types.TimeSeries, Types.TimeSeries.Builder, Types.TimeSeriesOrBuilder>
+                    timeseriesBuilder_;
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public java.util.List<Types.TimeSeries> getTimeseriesList() {
+                if (timeseriesBuilder_ == null) {
+                    return java.util.Collections.unmodifiableList(timeseries_);
+                } else {
+                    return timeseriesBuilder_.getMessageList();
+                }
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public int getTimeseriesCount() {
+                if (timeseriesBuilder_ == null) {
+                    return timeseries_.size();
+                } else {
+                    return timeseriesBuilder_.getCount();
+                }
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Types.TimeSeries getTimeseries(int index) {
+                if (timeseriesBuilder_ == null) {
+                    return timeseries_.get(index);
+                } else {
+                    return timeseriesBuilder_.getMessage(index);
+                }
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Builder setTimeseries(int index, Types.TimeSeries value) {
+                if (timeseriesBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureTimeseriesIsMutable();
+                    timeseries_.set(index, value);
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.setMessage(index, value);
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Builder setTimeseries(int index, Types.TimeSeries.Builder builderForValue) {
+                if (timeseriesBuilder_ == null) {
+                    ensureTimeseriesIsMutable();
+                    timeseries_.set(index, builderForValue.build());
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.setMessage(index, builderForValue.build());
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Builder addTimeseries(Types.TimeSeries value) {
+                if (timeseriesBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureTimeseriesIsMutable();
+                    timeseries_.add(value);
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.addMessage(value);
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Builder addTimeseries(int index, Types.TimeSeries value) {
+                if (timeseriesBuilder_ == null) {
+                    if (value == null) {
+                        throw new NullPointerException();
+                    }
+                    ensureTimeseriesIsMutable();
+                    timeseries_.add(index, value);
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.addMessage(index, value);
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Builder addTimeseries(Types.TimeSeries.Builder builderForValue) {
+                if (timeseriesBuilder_ == null) {
+                    ensureTimeseriesIsMutable();
+                    timeseries_.add(builderForValue.build());
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.addMessage(builderForValue.build());
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Builder addTimeseries(int index, Types.TimeSeries.Builder builderForValue) {
+                if (timeseriesBuilder_ == null) {
+                    ensureTimeseriesIsMutable();
+                    timeseries_.add(index, builderForValue.build());
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.addMessage(index, builderForValue.build());
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Builder addAllTimeseries(Iterable<? extends Types.TimeSeries> values) {
+                if (timeseriesBuilder_ == null) {
+                    ensureTimeseriesIsMutable();
+                    com.google.protobuf.AbstractMessageLite.Builder.addAll(values, timeseries_);
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.addAllMessages(values);
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Builder clearTimeseries() {
+                if (timeseriesBuilder_ == null) {
+                    timeseries_ = java.util.Collections.emptyList();
+                    bitField0_ = (bitField0_ & ~0x00000001);
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.clear();
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Builder removeTimeseries(int index) {
+                if (timeseriesBuilder_ == null) {
+                    ensureTimeseriesIsMutable();
+                    timeseries_.remove(index);
+                    onChanged();
+                } else {
+                    timeseriesBuilder_.remove(index);
+                }
+                return this;
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Types.TimeSeries.Builder getTimeseriesBuilder(int index) {
+                return getTimeseriesFieldBuilder().getBuilder(index);
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Types.TimeSeriesOrBuilder getTimeseriesOrBuilder(int index) {
+                if (timeseriesBuilder_ == null) {
+                    return timeseries_.get(index);
+                } else {
+                    return timeseriesBuilder_.getMessageOrBuilder(index);
+                }
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public java.util.List<? extends Types.TimeSeriesOrBuilder>
+                    getTimeseriesOrBuilderList() {
+                if (timeseriesBuilder_ != null) {
+                    return timeseriesBuilder_.getMessageOrBuilderList();
+                } else {
+                    return java.util.Collections.unmodifiableList(timeseries_);
+                }
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Types.TimeSeries.Builder addTimeseriesBuilder() {
+                return getTimeseriesFieldBuilder()
+                        .addBuilder(Types.TimeSeries.getDefaultInstance());
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public Types.TimeSeries.Builder addTimeseriesBuilder(int index) {
+                return getTimeseriesFieldBuilder()
+                        .addBuilder(index, Types.TimeSeries.getDefaultInstance());
+            }
+
+            /**
+             *
+             *
+             * <pre>
+             * Samples within a time series must be ordered by time.
+             * </pre>
+             *
+             * <code>repeated .prometheus.TimeSeries timeseries = 1;</code>
+             */
+            public java.util.List<Types.TimeSeries.Builder> getTimeseriesBuilderList() {
+                return getTimeseriesFieldBuilder().getBuilderList();
+            }
+
+            private com.google.protobuf.RepeatedFieldBuilderV3<
+                            Types.TimeSeries, Types.TimeSeries.Builder, Types.TimeSeriesOrBuilder>
+                    getTimeseriesFieldBuilder() {
+                if (timeseriesBuilder_ == null) {
+                    timeseriesBuilder_ =
+                            new com.google.protobuf.RepeatedFieldBuilderV3<
+                                    Types.TimeSeries,
+                                    Types.TimeSeries.Builder,
+                                    Types.TimeSeriesOrBuilder>(
+                                    timeseries_,
+                                    ((bitField0_ & 0x00000001) != 0),
+                                    getParentForChildren(),
+                                    isClean());
+                    timeseries_ = null;
+                }
+                return timeseriesBuilder_;
+            }
+
+            @Override
+            public final Builder setUnknownFields(
+                    final com.google.protobuf.UnknownFieldSet unknownFields) {
+                return super.setUnknownFields(unknownFields);
+            }
+
+            @Override
+            public final Builder mergeUnknownFields(
+                    final com.google.protobuf.UnknownFieldSet unknownFields) {
+                return super.mergeUnknownFields(unknownFields);
+            }
+
+            // @@protoc_insertion_point(builder_scope:prometheus.QueryResult)
+        }
+
+        // @@protoc_insertion_point(class_scope:prometheus.QueryResult)
+        private static final QueryResult DEFAULT_INSTANCE;
+
+        static {
+            DEFAULT_INSTANCE = new QueryResult();
+        }
+
+        public static QueryResult getDefaultInstance() {
+            return DEFAULT_INSTANCE;
+        }
+
+        private static final com.google.protobuf.Parser<QueryResult> PARSER =
+                new com.google.protobuf.AbstractParser<QueryResult>() {
+                    @Override
+                    public QueryResult parsePartialFrom(
+                            com.google.protobuf.CodedInputStream input,
+                            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                            throws com.google.protobuf.InvalidProtocolBufferException {
+                        Builder builder = newBuilder();
+                        try {
+                            builder.mergeFrom(input, extensionRegistry);
+                        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+                            throw e.setUnfinishedMessage(builder.buildPartial());
+                        } catch (com.google.protobuf.UninitializedMessageException e) {
+                            throw e.asInvalidProtocolBufferException()
+                                    .setUnfinishedMessage(builder.buildPartial());
+                        } catch (java.io.IOException e) {
+                            throw new com.google.protobuf.InvalidProtocolBufferException(e)
+                                    .setUnfinishedMessage(builder.buildPartial());
+                        }
+                        return builder.buildPartial();
+                    }
+                };
+
+        public static com.google.protobuf.Parser<QueryResult> parser() {
+            return PARSER;
+        }
+
+        @Override
+        public com.google.protobuf.Parser<QueryResult> getParserForType() {
+            return PARSER;
+        }
+
+        @Override
+        public QueryResult getDefaultInstanceForType() {
+            return DEFAULT_INSTANCE;
+        }
+    }
+
+    public interface ChunkedReadResponseOrBuilder
+            extends
+            // @@protoc_insertion_point(interface_extends:prometheus.ChunkedReadResponse)
+            com.google.protobuf.MessageOrBuilder {
+
+        /** <code>repeated .prometheus.ChunkedSeries chunked_series = 1;</code> */
+        java.util.List<Types.ChunkedSeries> getChunkedSeriesList();
+
+        /** <code>repeated .prometheus.ChunkedSeries chunked_series = 1;</code> */
+        Types.ChunkedSeries getChunkedSeries(int index);
+
+        /** <code>repeated .prometheus.ChunkedSeries chunked_series = 1;</code> */
+        int getChunkedSeriesCount();
+
+        /** <code>repeated .prometheus.ChunkedSeries chunked_series = 1;</code> */
+        java.util.List<? extends Types.ChunkedSeriesOrBuilder> getChunkedSeriesOrBuilderList();
+
+        /** <code>repeated .prometheus.ChunkedSeries chunked_series = 1;</code> */
+        Types.ChunkedSeriesOrBuilder getChunkedSeriesOrBuilder(int index);
+
+        /**
+         *
+         *
+         * <pre>
+         * query_index represents an index of the query from ReadRequest.queries these chunks relates to.
+         * </pre>
+         *
+         * <code>int64 query_index = 2;</code>
+         *
+         * @return The queryIndex.
+         */
+        long getQueryIndex();
+    }
+
+    /**
+     *
+     *
+     * <pre>
+     * ChunkedReadResponse is a response when response_type equals STREAMED_XOR_CHUNKS.
+     * We strictly stream full series after series, optionally split by time. This means that a single frame can contain
+     * partition of the single series, but once a new series is started to be streamed it means that no more chunks will
+     * be sent for previous one. Series are returned sorted in the same way TSDB block are internally.
+     * </pre>
+     *
+     * <p>Protobuf type {@code prometheus.ChunkedReadResponse}
+     */
+    public static final class ChunkedReadResponse extends com.google.protobuf.GeneratedMessageV3
+            implements
+            // @@protoc_insertion_point(message_implements:prometheus.ChunkedReadResponse)
+            ChunkedReadResponseOrBuilder {
+        private static final long serialVersionUID = 0L;
+
+        // Use ChunkedReadResponse.newBuilder() to construct.
+        private ChunkedReadResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+            super(builder);
+        }
+
+        private ChunkedReadResponse() {
+            chunkedSeries_ = java.util.Collections.emptyList();
+        }
+
+        @Override
+        @SuppressWarnings({"unused"})
+        protected Object newInstance(UnusedPrivateParameter unused) {
+            return new ChunkedReadResponse();
+        }
+
+        public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
+            return internal_static_prometheus_ChunkedReadResponse_descriptor;
+        }
+
+        @Override
+        protected FieldAccessorTable internalGetFieldAccessorTable() {
+            return internal_static_prometheus_ChunkedReadResponse_fieldAccessorTable
+                    .ensureFieldAccessorsInitialized(ChunkedReadResponse.class, Builder.class);
+        }
+
+        public static final int CHUNKED_SERIES_FIELD_NUMBER = 1;
+
+        @SuppressWarnings("serial")
+        private java.util.List<Types.ChunkedSeries> chunkedSeries_;
+
+        /** <code>repeated .prometheus.ChunkedSeries chunked_series = 1;</code> */
+        @Override
+        public java.util.List<Types.ChunkedSeries> getChunkedSeriesList() {
+            return chunkedSeries_;
+        }
+
+        /** <code>repeated .prometheus.ChunkedSeries chunked_series = 1;</code> */
+        @Override
+        public java.util.List<? extends Types.ChunkedSeriesOrBuilder>
+                getChunkedSeriesOrBuilderList() {
+            return chunkedSeries_;
+        }
+
+        /** <code>repeated .prometheus.ChunkedSeries chunked_series = 1;</code> */
+        @Override
+        public int getChunkedSeriesCount() {
+            return chunkedSeries_.size();
+        }
+
+        /** <code>repeated .prometheus.ChunkedSeries chunked_series = 1;</code> */
+        @Override
+        public Types.ChunkedSeries getChunkedSeries(int index) {
+            return chunkedSeries_.get(index);
+        }
+
+        /** <code>repeated .prometheus.ChunkedSeries chunked_series = 1;</code> */
+        @Override
+        public Types.ChunkedSeriesOrBuilder getChunkedSeriesOrBuilder(int index) {
+            return chunkedSeries_.get(index);
+        }
+
+        public static final int QUERY_INDEX_FIELD_NUMBER = 2;
+        private long queryIndex_ = 0L;
+
+        /**
+         *
+         *
+         * <pre>
+         * query_index represents an index of the query from ReadRequest.queries these chunks relates to.
+         * </pre>
+         *
+         * <code>int64 query_index = 2;</code>
+         *
+         * @return The queryIndex.
+         */
+        @Override
+        public long getQueryIndex() {
+            return queryIndex_;
+        }
+
+        private byte memoizedIsInitialized = -1;
+
+        @Override
+        public final boolean isInitialized() {
+            byte isInitialized = memoizedIsInitialized;
+            if (isInitialized == 1) return true;
+            if (isInitialized == 0) return false;
+
+            memoizedIsInitialized = 1;
+            return true;
+        }
+
+        @Override
+        public void writeTo(com.google.protobuf.CodedOutputStream output)
+                throws java.io.IOException {
+            for (int i = 0; i < chunkedSeries_.size(); i++) {
+                output.writeMessage(1, chunkedSeries_.get(i));
+            }
+            if (queryIndex_ != 0L) {
+                output.writeInt64(2, queryIndex_);
+            }
+            getUnknownFields().writeTo(output);
+        }
+
+        @Override
+        public int getSerializedSize() {
+            int size = memoizedSize;
+            if (size != -1) return size;
+
+            size = 0;
+            for (int i = 0; i < chunkedSeries_.size(); i++) {
+                size +=
+                        com.google.protobuf.CodedOutputStream.computeMessageSize(
+                                1, chunkedSeries_.get(i));
+            }
+            if (queryIndex_ != 0L) {
+                size += com.google.protobuf.CodedOutputStream.computeInt64Size(2, queryIndex_);
+            }
+            size += getUnknownFields().getSerializedSize();
+            memoizedSize = size;
+            return size;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof ChunkedReadResponse)) {
+                return super.equals(obj);
+            }
+            ChunkedReadResponse other = (ChunkedReadResponse) obj;
+
+            if (!getChunkedSeriesList().equals(other.getChunkedSeriesList())) return false;
+            if (getQueryIndex() != other.getQueryIndex()) return false;
+            if (!getUnknownFields().equals(other.getUnknownFields())) return false;
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            if (memoizedHashCode != 0) {
+                return memoizedHashCode;
+            }
+            int hash = 41;
+            hash = (19 * hash) + getDescriptor().hashCode();
+            if (getChunkedSeriesCount() > 0) {
+                hash = (37 * hash) + CHUNKED_SERIES_FIELD_NUMBER;
+                hash = (53 * hash) + getChunkedSeriesList().hashCode();
+            }
+            hash = (37 * hash) + QUERY_INDEX_FIELD_NUMBER;
+            hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getQueryIndex());
+            hash = (29 * hash) + getUnknownFields().hashCode();
+            memoizedHashCode = hash;
+            return hash;
+        }
+
+        public static ChunkedReadResponse parseFrom(java.nio.ByteBuffer data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static ChunkedReadResponse parseFrom(
+                java.nio.ByteBuffer data,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static ChunkedReadResponse parseFrom(com.google.protobuf.ByteString data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static ChunkedReadResponse parseFrom(
+                com.google.protobuf.ByteString data,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static ChunkedReadResponse parseFrom(byte[] data)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data);
+        }
+
+        public static ChunkedReadResponse parseFrom(
+                byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws com.google.protobuf.InvalidProtocolBufferException {
+            return PARSER.parseFrom(data, extensionRegistry);
+        }
+
+        public static ChunkedReadResponse parseFrom(java.io.InputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
+        }
+
+        public static ChunkedReadResponse parseFrom(
+                java.io.InputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        public static ChunkedReadResponse parseDelimitedFrom(java.io.InputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(
+                    PARSER, input);
+        }
+
+        public static ChunkedReadResponse parseDelimitedFrom(
+                java.io.InputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        public static ChunkedReadResponse parseFrom(com.google.protobuf.CodedInputStream input)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
+        }
+
+        public static ChunkedReadResponse parseFrom(
+                com.google.protobuf.CodedInputStream input,
+                com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                throws java.io.IOException {
+            return com.google.protobuf.GeneratedMessageV3.parseWithIOException(
+                    PARSER, input, extensionRegistry);
+        }
+
+        @Override
+        public Builder newBuilderForType() {
+            return newBuilder();
+        }
+
+        public static Builder newBuilder() {
+            return DEFAULT_INSTANCE.toBuilder();
+        }
+
+        public static Builder newBuilder(ChunkedReadResponse prototype) {
+            return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+        }
+
+        @Override
+        public Builder toBuilder() {
+            return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this);
+        }
+
+        @Override
+        protected Builder newBuilderForType(BuilderParent parent) {
+            Builder builder = new Builder(parent);
+            return builder;
+        }
+
+        /**
+         *
+         *
+         * <pre>
+         * ChunkedReadResponse is a response when response_type equals STREAMED_XOR_CHUNKS.
+         * We strictly stream full series after series, optionally split by time. This means that a single frame can contain
+         * partition of the single series, but once a new series is started to be streamed it means that no more chunks will
+         * be sent for previous one. Series are returned sorted in the same way TSDB block are internally.
+         * </pre>
+         *
+         * <p>Protobuf type {@code prometheus.ChunkedReadResponse}
+         */
+        public static final class Builder
+                extends com.google.protobuf.GeneratedMessageV3.Builder<Builder>
+                implements
+                // @@protoc_insertion_point(builder_implements:prometheus.ChunkedReadResponse)
+                ChunkedReadResponseOrBuilder {
+            public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() {
+                return internal_static_prometheus_ChunkedReadResponse_descriptor;
+            }
+
+            @Override
+            protected FieldAccessorTable internalGetFieldAccessorTable() {
+                return internal_static_prometheus_ChunkedReadResponse_fieldAccessorTable
+                        .ensureFieldAccessorsInitialized(ChunkedReadResponse.class, Builder.class);
+            }
+
+            // Construct using
+            // org.apache.flink.connector.prometheus.sink.prometheus.Remote.ChunkedReadResponse.newBuilder()
+            private Builder() {}
+
+            private Builder(BuilderParent parent) {
+                super(parent);
+            }
+
+            @Override
+            public Builder clear() {
+                super.clear();
+                bitField0_ = 0;
+                if (chunkedSeriesBuilder_ == null) {
+                    chunkedSeries_ = java.util.Collections.emptyList();
+                } else {
+                    chunkedSeries_ = null;
+                    chunkedSeriesBuilder_.clear();
+                }
+                bitField0_ = (bitField0_ & ~0x00000001);
+                queryIndex_ = 0L;
+                return this;
+            }
+
+            @Override
+            public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() {
+                return internal_static_prometheus_ChunkedReadResponse_descriptor;
+            }
+
+            @Override
+            public ChunkedReadResponse getDefaultInstanceForType() {
+                return getDefaultInstance();
+            }
+
+            @Override
+            public ChunkedReadResponse build() {
+                ChunkedReadResponse result = buildPartial();
+                if (!result.isInitialized()) {
+                    throw newUninitializedMessageException(result);
+                }
+                return result;
+            }
+
+            @Override
+            public ChunkedReadResponse buildPartial() {
+                ChunkedReadResponse result = new ChunkedReadResponse(this);
+                buildPartialRepeatedFields(result);
+                if (bitField0_ != 0) {
+                    buildPartial0(result);
+                }
+                onBuilt();
+                return result;
+            }
+
+            private void buildPartialRepeatedFields(ChunkedReadResponse result) {
+                if (chunkedSeriesBuilder_ == null) {
+                    if (((bitField0_ & 0x00000001) != 0)) {
+                        chunkedSeries_ = java.util.Collections.unmodifiableList(chunkedSeries_);
+                        bitField0_ = (bitField0_ & ~0x00000001);
+                    }
+                    result.chunkedSeries_ = chunkedSeries_;
+                } else {
+                    result.chunkedSeries_ = chunkedSeriesBuilder_.build();
+                }
+            }
+
+            private void buildPartial0(ChunkedReadResponse result) {
+                int from_bitField0_ = bitField0_;
+                if (((from_bitField0_ & 0x00000002) != 0)) {
+                    result.queryIndex_ = queryIndex_;
+                }
+            }
+
+            @Override
+            public Builder mergeFrom(com.google.protobuf.Message other) {
+                if (other instanceof ChunkedReadResponse) {
+                    return mergeFrom((ChunkedReadResponse) other);
+                } else {
+                    super.mergeFrom(other);
+                    return this;
+                }
+            }
+
+            public Builder mergeFrom(ChunkedReadResponse other) {
+                if (other == getDefaultInstance()) return this;
+                if (chunkedSeriesBuilder_ == null) {
+                    if (!other.chunkedSeries_.isEmpty()) {
+                        if (chunkedSeries_.isEmpty()) {
+                            chunkedSeries_ = other.chunkedSeries_;
+                            bitField0_ = (bitField0_ & ~0x00000001);
+                        } else {
+                            ensureChunkedSeriesIsMutable();
+                            chunkedSeries_.addAll(other.chunkedSeries_);
+                        }
+                        onChanged();
+                    }
+                } else {
+                    if (!other.chunkedSeries_.isEmpty()) {
+                        if (chunkedSeriesBuilder_.isEmpty()) {
+                            chunkedSeriesBuilder_.dispose();
+                            chunkedSeriesBuilder_ = null;
+                            chunkedSeries_ = other.chunkedSeries_;
+                            bitField0_ = (bitField0_ & ~0x00000001);
+                            chunkedSeriesBuilder_ =
+                                    com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders
+                                            ? getChunkedSeriesFieldBuilder()
+                                            : null;
+                        } else {
+                            chunkedSeriesBuilder_.addAllMessages(other.chunkedSeries_);
+                        }
+                    }
+                }
+                if (other.getQueryIndex() != 0L) {
+                    setQueryIndex(other.getQueryIndex());
+                }
+                this.mergeUnknownFields(other.getUnknownFields());
+                onChanged();
+                return this;
+            }
+
+            @Override
+            public final boolean isInitialized() {
+                return true;
+            }
+
+            @Override
+            public Builder mergeFrom(
+                    com.google.protobuf.CodedInputStream input,
+                    com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+                    throws java.io.IOException {
+                if (extensionRegistry == null) {
+                    throw new NullPointerException();
+                }
+                try {
+                    boolean done = false;
+                    while (!done) {
+                        int tag = input.readTag();
+                        switch (tag) {
+                            case 0:
+                                done = true;
+                                break;
+                            case 10:
... 21599 lines suppressed ...