[ 
https://issues.apache.org/jira/browse/HDDS-16742?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Andrey Yarovoy updated HDDS-16742:
----------------------------------
    Description: 
To populate the `LatestRequestType` tag, 
`GrpcMetricsServerRequestInterceptor.onMessage` does:
{code:java}
String[] messageFields = message.toString()
.split(System.lineSeparator());
// messageFields[0] should be in the format
// cmdType: "type"
String[] cmdTypeLine = messageFields[0].split(":");

// Get only the type and remove any leading spaces
grpcMetrics.setRequestType(cmdTypeLine[1].trim());
{code}
`AbstractMessage.toString()` is a full `TextFormat` render of the *entire* 
request. On this server the
request is an `OMRequest`, so every call allocates a text rendering of the 
whole message — including
nested key, ACL and list payloads — splits it into an array of lines, and then 
uses exactly one line
before discarding all of it. For a large request this costs far more than 
everything else the metrics
interceptors do combined.

`cmdType` is field 1 of `OMRequest` and is directly readable. Keeping the 
interceptor generic over
`ReqT` (and avoiding a `hadoop-hdds/framework` → 
`hadoop-ozone/interface-client` dependency), the
descriptor is enough:
{code:java}
Descriptors.FieldDescriptor cmdType =
parsedMessage.getDescriptorForType().findFieldByName("cmdType");
if (cmdType != null && parsedMessage.hasField(cmdType)) {
grpcMetrics.setRequestType(parsedMessage.getField(cmdType).toString());
}
{code}
The current parsing is also fragile: it assumes `cmdType` is rendered first, 
that the value contains no
`:`, and that `messageFields[0]` splits into at least two parts. A request 
whose first line is not
`cmdType: …` yields `cmdTypeLine` of length 1 and throws 
`ArrayIndexOutOfBoundsException` from inside a
server interceptor. Reading the field removes all three assumptions.

Two related issues in the same area, in scope for this Jira since they concern 
the same field:
 - `GrpcMetrics.getMetrics()` is `synchronized` but `setRequestType()` is not, 
so the synchronization
does not establish the happens-before it looks like it does for `requestType`.
 - `requestType` is a single server-wide field, so `LatestRequestType` is 
inherently whichever concurrent
call wrote last. That may be acceptable for a tag literally named "latest", but 
it should be made
deliberate — at minimum `volatile` — rather than left as an unsynchronized race.

*Testing*

Unit test on the interceptor with a mocked `ServerCall` / `ServerCallHandler`: 
drive `onMessage` with an
`OMRequest` and assert `GrpcMetrics.getRequestType()` returns the `cmdType` 
value, including for a
request whose other fields contain `:` and newlines, which the current string 
parsing gets wrong.

  was:
To populate the `LatestRequestType` tag, 
`GrpcMetricsServerRequestInterceptor.onMessage` does:

{code}
String[] messageFields = message.toString()
.split(System.lineSeparator());
// messageFields[0] should be in the format
// cmdType: "type"
String[] cmdTypeLine = messageFields[0].split(":");

// Get only the type and remove any leading spaces
grpcMetrics.setRequestType(cmdTypeLine[1].trim());
{code}

`AbstractMessage.toString()` is a full `TextFormat` render of the *entire* 
request. On this server the
request is an `OMRequest`, so every call allocates a text rendering of the 
whole message — including
nested key, ACL and list payloads — splits it into an array of lines, and then 
uses exactly one line
before discarding all of it. For a large request this costs far more than 
everything else the metrics
interceptors do combined.

`cmdType` is field 1 of `OMRequest` and is directly readable. Keeping the 
interceptor generic over
`ReqT` (and avoiding a `hadoop-hdds/framework` → 
`hadoop-ozone/interface-client` dependency), the
descriptor is enough:

{code}
Descriptors.FieldDescriptor cmdType =
parsedMessage.getDescriptorForType().findFieldByName("cmdType");
if (cmdType != null && parsedMessage.hasField(cmdType)) {
grpcMetrics.setRequestType(parsedMessage.getField(cmdType).toString());
}
{code}

The current parsing is also fragile: it assumes `cmdType` is rendered first, 
that the value contains no
`:`, and that `messageFields[0]` splits into at least two parts. A request 
whose first line is not
`cmdType: …` yields `cmdTypeLine` of length 1 and throws 
`ArrayIndexOutOfBoundsException` from inside a
server interceptor. Reading the field removes all three assumptions.

Two related issues in the same area, in scope for this Jira since they concern 
the same field:

- `GrpcMetrics.getMetrics()` is `synchronized` but `setRequestType()` is not, 
so the synchronization
does not establish the happens-before it looks like it does for `requestType`.
- `requestType` is a single server-wide field, so `LatestRequestType` is 
inherently whichever concurrent
call wrote last. That may be acceptable for a tag literally named "latest", but 
it should be made
deliberate — at minimum `volatile` — rather than left as an unsynchronized race.

### Testing

Unit test on the interceptor with a mocked `ServerCall` / `ServerCallHandler`: 
drive `onMessage` with an
`OMRequest` and assert `GrpcMetrics.getRequestType()` returns the `cmdType` 
value, including for a
request whose other fields contain `:` and newlines, which the current string 
parsing gets wrong.


> GrpcMetricsServerRequestInterceptor renders every request to protobuf text 
> format to read one field
> ---------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-16742
>                 URL: https://issues.apache.org/jira/browse/HDDS-16742
>             Project: Apache Ozone
>          Issue Type: Improvement
>          Components: OM
>            Reporter: Andrey Yarovoy
>            Assignee: Andrey Yarovoy
>            Priority: Major
>
> To populate the `LatestRequestType` tag, 
> `GrpcMetricsServerRequestInterceptor.onMessage` does:
> {code:java}
> String[] messageFields = message.toString()
> .split(System.lineSeparator());
> // messageFields[0] should be in the format
> // cmdType: "type"
> String[] cmdTypeLine = messageFields[0].split(":");
> // Get only the type and remove any leading spaces
> grpcMetrics.setRequestType(cmdTypeLine[1].trim());
> {code}
> `AbstractMessage.toString()` is a full `TextFormat` render of the *entire* 
> request. On this server the
> request is an `OMRequest`, so every call allocates a text rendering of the 
> whole message — including
> nested key, ACL and list payloads — splits it into an array of lines, and 
> then uses exactly one line
> before discarding all of it. For a large request this costs far more than 
> everything else the metrics
> interceptors do combined.
> `cmdType` is field 1 of `OMRequest` and is directly readable. Keeping the 
> interceptor generic over
> `ReqT` (and avoiding a `hadoop-hdds/framework` → 
> `hadoop-ozone/interface-client` dependency), the
> descriptor is enough:
> {code:java}
> Descriptors.FieldDescriptor cmdType =
> parsedMessage.getDescriptorForType().findFieldByName("cmdType");
> if (cmdType != null && parsedMessage.hasField(cmdType)) {
> grpcMetrics.setRequestType(parsedMessage.getField(cmdType).toString());
> }
> {code}
> The current parsing is also fragile: it assumes `cmdType` is rendered first, 
> that the value contains no
> `:`, and that `messageFields[0]` splits into at least two parts. A request 
> whose first line is not
> `cmdType: …` yields `cmdTypeLine` of length 1 and throws 
> `ArrayIndexOutOfBoundsException` from inside a
> server interceptor. Reading the field removes all three assumptions.
> Two related issues in the same area, in scope for this Jira since they 
> concern the same field:
>  - `GrpcMetrics.getMetrics()` is `synchronized` but `setRequestType()` is 
> not, so the synchronization
> does not establish the happens-before it looks like it does for `requestType`.
>  - `requestType` is a single server-wide field, so `LatestRequestType` is 
> inherently whichever concurrent
> call wrote last. That may be acceptable for a tag literally named "latest", 
> but it should be made
> deliberate — at minimum `volatile` — rather than left as an unsynchronized 
> race.
> *Testing*
> Unit test on the interceptor with a mocked `ServerCall` / 
> `ServerCallHandler`: drive `onMessage` with an
> `OMRequest` and assert `GrpcMetrics.getRequestType()` returns the `cmdType` 
> value, including for a
> request whose other fields contain `:` and newlines, which the current string 
> parsing gets wrong.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to