Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,24 @@ Pinecone pinecone = new Pinecone.Builder("PINECONE_API_KEY")
metadata.getClientDurationMs(),
metadata.getServerDurationMs(),
metadata.getNetworkOverheadMs());

// Log usage and operation-result fields when available
if (metadata.getReadUnits() != null) {
System.out.printf(" Read units consumed: %d%n", metadata.getReadUnits());
}
if (metadata.getUpsertedCount() != null) {
System.out.printf(" Upserted count: %d%n", metadata.getUpsertedCount());
}
if (metadata.getMatchedRecords() != null) {
System.out.printf(" Matched records: %d%n", metadata.getMatchedRecords());
}
})
.build();

Index index = pinecone.getIndexConnection("example-index");
index.query(5, Arrays.asList(1.0f, 2.0f, 3.0f));
// Output: Operation: query | Client: 45ms | Server: 32ms | Network: 13ms
// Read units consumed: 5
```

The `ResponseMetadata` object provides:
Expand All @@ -189,6 +201,9 @@ The `ResponseMetadata` object provides:
| `isSuccess()` | Whether the operation succeeded |
| `getGrpcStatusCode()` | gRPC status code |
| `getErrorType()` | Error category when failed |
| `getReadUnits()` | Read units consumed (available on query, fetch, list) |
| `getUpsertedCount()` | Number of vectors upserted (available on upsert) |
| `getMatchedRecords()` | Number of matched records (available on update) |

For a complete OpenTelemetry integration example with Prometheus and Grafana, see the [java-otel-metrics example](examples/java-otel-metrics/).

Expand Down
60 changes: 60 additions & 0 deletions src/main/java/io/pinecone/configs/ResponseMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
* <li>{@link #getClientDurationMs()} - Total round-trip time measured by the client</li>
* <li>{@link #getServerDurationMs()} - Server processing time from x-pinecone-response-duration-ms header</li>
* <li>{@link #getNetworkOverheadMs()} - Computed network + serialization overhead</li>
* <li>{@link #getReadUnits()} - Number of read units consumed (query, fetch, list operations)</li>
* <li>{@link #getUpsertedCount()} - Number of records upserted (upsert operations)</li>
* <li>{@link #getMatchedRecords()} - Number of records matched (update operations)</li>
* </ul>
*
* <p>Example usage with a listener:
Expand All @@ -34,6 +37,9 @@ public class ResponseMetadata {
private final String status;
private final String grpcStatusCode;
private final String errorType;
private final Integer readUnits;
private final Integer upsertedCount;
private final Integer matchedRecords;

private ResponseMetadata(Builder builder) {
this.operationName = builder.operationName;
Expand All @@ -45,6 +51,9 @@ private ResponseMetadata(Builder builder) {
this.status = builder.status;
this.grpcStatusCode = builder.grpcStatusCode;
this.errorType = builder.errorType;
this.readUnits = builder.readUnits;
this.upsertedCount = builder.upsertedCount;
this.matchedRecords = builder.matchedRecords;
}

/**
Expand Down Expand Up @@ -134,6 +143,30 @@ public String getErrorType() {
return errorType;
}

/**
* Returns the number of read units consumed by the operation, or null if not applicable.
* Populated for query, fetch, and list operations.
*/
public Integer getReadUnits() {
return readUnits;
}

/**
* Returns the number of records upserted, or null if not applicable.
* Populated for upsert operations.
*/
public Integer getUpsertedCount() {
return upsertedCount;
}

/**
* Returns the number of records matched by the operation, or null if not applicable.
* Populated for update operations.
*/
public Integer getMatchedRecords() {
return matchedRecords;
}

/**
* Returns true if the operation was successful.
*/
Expand Down Expand Up @@ -162,6 +195,15 @@ public String toString() {
if (errorType != null) {
sb.append(", errorType=").append(errorType);
}
if (readUnits != null) {
sb.append(", readUnits=").append(readUnits);
}
if (upsertedCount != null) {
sb.append(", upsertedCount=").append(upsertedCount);
}
if (matchedRecords != null) {
sb.append(", matchedRecords=").append(matchedRecords);
}
sb.append("}");
return sb.toString();
}
Expand All @@ -176,6 +218,9 @@ public static class Builder {
private String status = "success";
private String grpcStatusCode = "OK";
private String errorType;
private Integer readUnits;
private Integer upsertedCount;
private Integer matchedRecords;

public Builder operationName(String operationName) {
this.operationName = operationName;
Expand Down Expand Up @@ -222,6 +267,21 @@ public Builder errorType(String errorType) {
return this;
}

public Builder readUnits(Integer readUnits) {
this.readUnits = readUnits;
return this;
}

public Builder upsertedCount(Integer upsertedCount) {
this.upsertedCount = upsertedCount;
return this;
}

public Builder matchedRecords(Integer matchedRecords) {
this.matchedRecords = matchedRecords;
return this;
}

public ResponseMetadata build() {
return new ResponseMetadata(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class ResponseMetadataInterceptor implements ClientInterceptor {

// Operations to track (matches VectorService RPC method names)
private static final Set<String> TRACKED_OPERATIONS = new HashSet<>(Arrays.asList(
"Upsert", "Query", "Fetch", "Update", "Delete"
"Upsert", "Query", "Fetch", "Update", "Delete", "List"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use a LOT of list operations. This is necessary for us.

));

private final ResponseMetadataListener listener;
Expand Down Expand Up @@ -75,6 +75,9 @@ public void start(Listener<RespT> responseListener, Metadata headers) {
responseListener) {

private Metadata initialHeaders;
private Integer readUnits;
private Integer upsertedCount;
private Integer matchedRecords;

@Override
public void onHeaders(Metadata headers) {
Expand All @@ -86,6 +89,19 @@ public void onHeaders(Metadata headers) {
super.onHeaders(headers);
}

@Override
public void onMessage(RespT message) {
// Extract response body fields via reflection
try {
readUnits = extractReadUnits(message);
upsertedCount = extractUpsertedCount(message);
matchedRecords = extractMatchedRecords(message);
} catch (Exception e) {
logger.debug("Could not extract response body fields", e);
}
super.onMessage(message);
}

@Override
public void onClose(Status status, Metadata trailers) {
try {
Expand Down Expand Up @@ -116,6 +132,9 @@ public void onClose(Status status, Metadata trailers) {
.status(statusStr)
.grpcStatusCode(status.getCode().name())
.errorType(errorType)
.readUnits(readUnits)
.upsertedCount(upsertedCount)
.matchedRecords(matchedRecords)
.build();

invokeListener(metadata);
Expand Down Expand Up @@ -159,6 +178,58 @@ private <T> String extractNamespace(T message) {
}
}

private <T> Integer extractReadUnits(T message) {
// Extract read units from QueryResponse, FetchResponse, ListResponse via hasUsage()/getUsage()/getReadUnits()
try {
java.lang.reflect.Method hasUsage = message.getClass().getMethod("hasUsage");
Boolean has = (Boolean) hasUsage.invoke(message);
if (Boolean.TRUE.equals(has)) {
java.lang.reflect.Method getUsage = message.getClass().getMethod("getUsage");
Object usage = getUsage.invoke(message);
java.lang.reflect.Method getReadUnits = usage.getClass().getMethod("getReadUnits");
Object result = getReadUnits.invoke(usage);
return ((Number) result).intValue();
}
} catch (NoSuchMethodException e) {
// Response type does not have usage field
} catch (Exception e) {
logger.debug("Could not extract read units from response", e);
}
return null;
}

private <T> Integer extractUpsertedCount(T message) {
// Extract upserted count from UpsertResponse via getUpsertedCount()
try {
java.lang.reflect.Method getUpsertedCount = message.getClass().getMethod("getUpsertedCount");
Object result = getUpsertedCount.invoke(message);
return ((Number) result).intValue();
} catch (NoSuchMethodException e) {
// Response type does not have upsertedCount field
} catch (Exception e) {
logger.debug("Could not extract upserted count from response", e);
}
return null;
}

private <T> Integer extractMatchedRecords(T message) {
// Extract matched records from UpdateResponse via hasMatchedRecords()/getMatchedRecords()
try {
java.lang.reflect.Method hasMatchedRecords = message.getClass().getMethod("hasMatchedRecords");
Boolean has = (Boolean) hasMatchedRecords.invoke(message);
if (Boolean.TRUE.equals(has)) {
java.lang.reflect.Method getMatchedRecords = message.getClass().getMethod("getMatchedRecords");
Object result = getMatchedRecords.invoke(message);
return ((Number) result).intValue();
}
} catch (NoSuchMethodException e) {
// Response type does not have matchedRecords field
} catch (Exception e) {
logger.debug("Could not extract matched records from response", e);
}
return null;
}

private String mapGrpcStatusToErrorType(Status.Code code) {
switch (code) {
case INVALID_ARGUMENT:
Expand Down
116 changes: 116 additions & 0 deletions src/test/java/io/pinecone/configs/ResponseMetadataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ void testBuilderWithAllFields() {
.serverDurationMs(100L)
.status("success")
.grpcStatusCode("OK")
.readUnits(5)
.upsertedCount(10)
.matchedRecords(3)
.build();

assertEquals("query", metadata.getOperationName());
Expand All @@ -29,6 +32,9 @@ void testBuilderWithAllFields() {
assertEquals("OK", metadata.getGrpcStatusCode());
assertTrue(metadata.isSuccess());
assertNull(metadata.getErrorType());
assertEquals(5, metadata.getReadUnits());
assertEquals(10, metadata.getUpsertedCount());
assertEquals(3, metadata.getMatchedRecords());
}

@Test
Expand Down Expand Up @@ -180,5 +186,115 @@ void testAllErrorTypes() {
assertFalse(metadata.isSuccess());
}
}

@Test
void testReadUnitsNullByDefault() {
ResponseMetadata metadata = ResponseMetadata.builder()
.operationName("query")
.build();

assertNull(metadata.getReadUnits());
}

@Test
void testReadUnitsWithValue() {
ResponseMetadata metadata = ResponseMetadata.builder()
.operationName("query")
.readUnits(10)
.build();

assertEquals(10, metadata.getReadUnits());
}

@Test
void testUpsertedCountNullByDefault() {
ResponseMetadata metadata = ResponseMetadata.builder()
.operationName("upsert")
.build();

assertNull(metadata.getUpsertedCount());
}

@Test
void testUpsertedCountWithValue() {
ResponseMetadata metadata = ResponseMetadata.builder()
.operationName("upsert")
.upsertedCount(100)
.build();

assertEquals(100, metadata.getUpsertedCount());
}

@Test
void testMatchedRecordsNullByDefault() {
ResponseMetadata metadata = ResponseMetadata.builder()
.operationName("update")
.build();

assertNull(metadata.getMatchedRecords());
}

@Test
void testMatchedRecordsWithValue() {
ResponseMetadata metadata = ResponseMetadata.builder()
.operationName("update")
.matchedRecords(42)
.build();

assertEquals(42, metadata.getMatchedRecords());
}

@Test
void testToStringWithReadUnits() {
ResponseMetadata metadata = ResponseMetadata.builder()
.operationName("query")
.indexName("my-index")
.clientDurationMs(100)
.readUnits(5)
.build();

String str = metadata.toString();
assertTrue(str.contains("readUnits=5"));
}

@Test
void testToStringWithUpsertedCount() {
ResponseMetadata metadata = ResponseMetadata.builder()
.operationName("upsert")
.indexName("my-index")
.clientDurationMs(100)
.upsertedCount(50)
.build();

String str = metadata.toString();
assertTrue(str.contains("upsertedCount=50"));
}

@Test
void testToStringWithMatchedRecords() {
ResponseMetadata metadata = ResponseMetadata.builder()
.operationName("update")
.indexName("my-index")
.clientDurationMs(100)
.matchedRecords(7)
.build();

String str = metadata.toString();
assertTrue(str.contains("matchedRecords=7"));
}

@Test
void testToStringOmitsNullResponseBodyFields() {
ResponseMetadata metadata = ResponseMetadata.builder()
.operationName("delete")
.indexName("my-index")
.clientDurationMs(100)
.build();

String str = metadata.toString();
assertFalse(str.contains("readUnits"));
assertFalse(str.contains("upsertedCount"));
assertFalse(str.contains("matchedRecords"));
}
}

Loading