Skip to content
This repository has been archived by the owner on May 23, 2023. It is now read-only.

Simple layer on top of ByteBuffer for BINARY format. #276

Merged

Conversation

carlosalberto
Copy link
Collaborator

Provide a simple layout to let users get/set the used ByteBuffer,
as well as hint them about the ByteBuffer length in the case
of injection (as opposed as the current approach, where they
need to pass a large-enough ByteBuffer when calling Tracer.inject()).

Observe this is merely a thin layer on top of ByteBuffer, but with the hint for letting the users know the required length of the buffer used by inject().

Provide a simple layout to let users get/set the used ByteBuffer,
as well as hint them about the ByteBuffer length in the case
of injection (as opposed as the current approach, where they
need to pass a large-enough ByteBuffer when calling Tracer.inject()).
@coveralls
Copy link

coveralls commented May 28, 2018

Coverage Status

Coverage increased (+1.3%) to 82.828% when pulling 31e458b on carlosalberto:binary_format_proposal_simple into 4c5dfb5 on opentracing:v0.32.0.

Copy link
Member

@yurishkuro yurishkuro left a comment

Choose a reason for hiding this comment

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

This seems like a reasonable compromise. It would be interesting to try writing an instrumentation example that pools the buffers to minimize the allocations, as it seems like w a lot of objects are being created during inject/extract.


import java.nio.ByteBuffer;

public final class Adapters {
Copy link
Member

Choose a reason for hiding this comment

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

what is the benefit of putting helpers here instead of BinaryAdapters.injectCarrier()?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I remember we had this idea of putting all the adapters in a single place (including the TextMap ones) - else, I can definitely make BinaryAdapters public, and remove the Adapters class.

Copy link
Member

Choose a reason for hiding this comment

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

I don't mind a shared class, but I'm bothered by the name "injectBinary", it reads like a command. We should come up with a more intuitive name, eg injectionCarrier, which works better with BinaryAdapters class.


private BinaryAdapters() {}

public static class BinaryExtractAdapter implements Binary {
Copy link
Member

Choose a reason for hiding this comment

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

does it have to be public?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In the current way, yes. But we can hide them through a single method (as we do now for Adapters ;) )

binary.injectBuffer().put(buff);

} catch (IOException e) {
throw new RuntimeException("Corrupted state");
Copy link
Member

Choose a reason for hiding this comment

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

should wrap the cause

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Makes sense. Will do.

while (objStream.available() > 0) {
baggage.put(objStream.readUTF(), objStream.readUTF());
}
} catch (IOException e) {
Copy link
Member

Choose a reason for hiding this comment

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

rethrow as wrapped?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So I'm wondering about this one - the TEXT_MAP format returns nothing (null) if the context is malformed (i.e. trace_id but no span_id, or nothing at all, etc). Should we re-throw the Exception anyway here, though?

Copy link
Member

Choose a reason for hiding this comment

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

I think for mock tracer it's better to throw on malformed carrier. Null is only when http header is missing (which doesn't apply to binary codecs).

}
}
} else {
throw new IllegalArgumentException("Unknown carrier");
Copy link
Member

Choose a reason for hiding this comment

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

I would move this to the top to reduce nesting

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Will do as well.

}
}
} else {
throw new IllegalArgumentException("Unknown carrier");
Copy link
Member

Choose a reason for hiding this comment

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

I would move this to the top to reduce nesting. Also clarify "expecting Binary, received " + carrier.getClass()

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agreed ;)

@carlosalberto
Copy link
Collaborator Author

The code was updated to show the suggested improvements @yurishkuro Let me know ;)

*
* @return The buffer used for SpanContext injection.
*/
ByteBuffer injectBuffer();
Copy link
Member

Choose a reason for hiding this comment

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

maybe injectionBuffer to mirror injectionCarrier in the adapter?

@carlosalberto
Copy link
Collaborator Author

@yurishkuro Good point (on the injectionBuffer instead of injectBuffer) ;)

Updated.

Copy link
Member

@yurishkuro yurishkuro left a comment

Choose a reason for hiding this comment

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

lgtm. I wonder if it would make sense to keep backwards compatibility and not change the BINARY format but deprecated it and introduce a new one. Do we know if people are using the current BINARY?

@carlosalberto
Copy link
Collaborator Author

@yurishkuro My impression is that nobody has been really using this API so far - still, I think it would be nice to ask on Gitter (at least), in case anybody was (if that's the case, we should definitely deprecate what we have now, etc).

Copy link
Contributor

@felixbarny felixbarny left a comment

Choose a reason for hiding this comment

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

@raphw /Instana is using BINARY. He also suggested some changes to the binary format to allow for streaming. See #243. I don't think these changes accommodate for this.

Also, please make the changes backwards compatible.

@hypnoce
Copy link
Contributor

hypnoce commented Jun 2, 2018

I believe this interface puts

  • more work on the instrumenters side, since it now has to handle the lifecycle of a ByteBuffer and an additional method to implement.
  • more work on the tracer implementers with an additional method setInjectionBufferLength to use. Also, it does not have the hand on the kind of buffer being created.
  • does not solve the concerns of @raphw

The previous interface where ByteBuffers were passed to the carrier did solve the first 2 items.

What do you think ?

@carlosalberto
Copy link
Collaborator Author

@felixbarny Oh, if Instana is using BINARY, then we definitely add a new format and deprecate the current one.

On @raphw and his feedback, I'd suggest reading, for historical context:

the more I think of it, I believe that it would actually be the easiest to extract a TextMap and push it into the stream depending on the underlying binary protocol. Until there is an actual use case, maybe it makes sense to table the binary extraction?

I had previously in the same thread mentioned the advantages and disadvantages of what the options we had at hand - which leads us to have this simple approach (which I mentioned later on as well):

definitely we need a simple, working approach for now (needed, for example, for instrumenting Cassandra on the server side). @raphw needs a more advanced, streaming-based approach that we could add later on, definitely, once we hit that need ;)

I'd urge you to read that to understand the full picture, so the situation is clear, and why we will go with this very PR for the time being. Else, let us know on your take (either for this PR still, or a future streaming based API ;) )

@carlosalberto
Copy link
Collaborator Author

@hypnoce Hey, setInjectionBufferLength() serves for users asking to have a variable-size ByteBuffer approach (see for example #99).

As the other questions (will write my answers here, but it sounds we should put them in the docs as well ;) )

more work on on instrumenters side, since it now has to handle the lifecycle of a ByteBuffer and an additionnal method to implement.

Related to the previous point - and users dont have to handle the lifecycle of a ByteBuffer - we simply provide a default adapter for users to want to have a new ByteBuffer created for simple calls (maybe for MockTracer). In a real world scenario, the user would have a ByteBuffer pool and have a Binary implementation like this (snippet):

class CustomBinary implements Binary
{
  public void setInjectionBufferLength(int length)
  {
    this.buffer = CustomByteBufferPool.getWithLength(length);
  }
  public ByteBuffer injectionBuffer()
  {
    return this.buffer;
  }
}

more work on the tracer implementers with an aditionnal method setInjectionBufferLength

Implementing an extra method is less work than implementing, for users, a full OutputStream or InputStream that has been described in #243, for example :) Also, it serves a specific purpose (and for Tracer.extract(), it should be straightforward to use our adapter (which means: no need to write a class for it):

Byte buffer = ...;
SpanContext ctx = tracer.extract(BinaryAdapters.extractionCarrier(buffer));

For the third point, see my answer to @felixbarny on why we decided to have that approach (streaming) as a separate, potential new FORMAT for the future.

The previous interface from @yurishkuro did solve the first 2 items.

Which one? #252? I think that's a fine API too, but this is simpler and would work just fine for simpler cases.

@tedsuo
Copy link
Member

tedsuo commented Jun 2, 2018

Glad to see interest in this picking up! I'm pinging more people to make them aware of this thread.

In the meantime, I would strongly suggest moving to tests and runnable code examples for illustrating various scenarios/points-of-concern. The ability for english/prose to move these discussions forwards becomes very limited once we get down into the weeds.

@tedsuo
Copy link
Member

tedsuo commented Jun 2, 2018

I would also like to suggest that there may be no "universal" binary solution that would satisfy every case effectively. In Java for example, we may want one solution for situations that require a ByteBuffer, and a separate solution for streaming, rather than a single solution. If a single underlying solution can be made efficient, with higher-level utilities being employed to make it ergonomic for various scenarios, that would be great. But if that seems too difficult, consider that we could instead have several separate solutions.

@hypnoce
Copy link
Contributor

hypnoce commented Jun 3, 2018

@carlosalberto Thanks for your answer !

In a real world scenario, the user would have a ByteBuffer pool and have a Binary implementation like this

Each middleware instrumenter would have an implementation of a ByteBuffer pool instead of the tracer implementation to handle it. Therefore, since a single JVM may host different middlewares but a single tracer implementation, you may endup using many ByteBuffer pools with variable degrees of stability.
As a middleware instrumenter, I would rather have the tracer to handle the pooling instead of me and the others since we will endup in many ways of implementing the same behavior. Hope you see the point here :).

Implementing an extra method is less work than implementing, for users, a full OutputStream or InputStream

Totally agree

Hey, setInjectionBufferLength() serves for users asking to have a variable-size ByteBuffer approach

Indeed, when the ByteBuffer is supplied by the instrumenter, then we need such method. When the BB is supplied by the tracer, then I believe this method disappears.

Which one? #252? I think that's a fine API too, but this is simpler and would work just fine for simpler cases.

Exactly. In what aspect do you think the new proposed one is simpler ?

In the end, I would emphasise that java.nio package is not asynchronous IO. It's New-IO and therefore does not enforce asynchronicity. Therefore, exposing ByteBuffer makes total sense in case of blocking IO.

Thanks for your great work !

@carlosalberto
Copy link
Collaborator Author

Hey @hypnoce, thanks for the message.

In what aspect do you think the new proposed one is simpler ?

I think this one is barely a thin layer on top of the current approach (a bare ByeBuffer object). Also, the other implementation you mentioned could become the future BINARY_STREAM one if/when needed (or else @raphw approach of working with actual Java IO streams). This would keep them separated.

As a middleware instrumenter, I would rather have the tracer to handle the pooling instead of me and the others since we will endup in many ways of implementing the same behavior.

Not sure Tracer implementation is the best place to put this, and not sure they would love it ;) To me it sounds we should provide some ByteBuffer pool in some opentracing-contrib repo in the near future, so other instrumentations could use it. This way you could import such pool and do:

import io.opentracing.contrib.BinaryPool;

// returns an object providing a ByteBuffer in the pool, based on a given length.
Binary binary = binaryPool.getInstance();

// Do the actual injection.
tracer.inject(ctx, Format.BINARY, binary);

// Use the result (provided by the pool)
ByteBuffer buffer = binary.injectionBuffer();
buffer.rewind();
request.setPayload(buffer);

This way, we provide a safe place to start - and later people can use their own implementation if they need to. Thing this would work for you? Let us know ;)

@carlosalberto
Copy link
Collaborator Author

Hey @CodingFabian I've got a question for you ;)

Is Instana is using the current BINARY format? Let us know, as we are moving towards a new API (more or less soon ;) ). Depending on your needs, we could keep the current one (as Deprecated) and create this mentioned new format under a new field (BINARY_LAYER or similar ;) )

Thanks in advance!

@CodingFabian
Copy link

well some of our implementations support it, not sure if any user uses it.
there are actually many open issues with the binary format, so I am not sure.

@hypnoce
Copy link
Contributor

hypnoce commented Jun 6, 2018

@carlosalberto thanks for your answer
I understand the rationales and it's a good idea to create a default Binary pool implementation in the contrib section.

I'm still not really confortable with the setInjectionLength(int length) method. It is very coupled with injectionBuffer().

Maye I would rather replace it by an overloading of injectionBuffer() :

public interface Binary {

    ByteBuffer injectionBuffer();

    ByteBuffer injectionBuffer(int length);

    ByteBuffer extractionBuffer();
}

I beleive it makes the Binary interface easier to understand just by reading the methods. Also, It does not impose some ordering constraints on the calls.

What do you think ?

@carlosalberto
Copy link
Collaborator Author

carlosalberto commented Jun 7, 2018

@hypnoce

Hey! So I had previously played with such approach (injectionBuffer(int length)), but abandoned it as the user couldn't easily/clearly retrieve the underlying buffer (what should happen if the user calls it again? clear the existing buffer? create a new one? what if another length is passed? etc). Having a simple getter for the underlying buffer was specially helpful for BinaryAdapters.injectionBuffer(), as it simply created a buffer on demand ;)

ByteBuffer injectionBuffer();
ByteBuffer injectionBuffer(int length);

IHMO this is confusing - one of them is used for creation, and another one as a getter, but with the same name... I'd rather stick with (as mentioned):

ByteBuffer injectionBuffer(int length);
ByteBuffer extractionBuffer();

And the user could access the underlying buffer depending on the actual implementation they have (CustomBinary.buffer() or similar).

(In this case we could remove the BinaryAdapters.injectionBuffer() overload in favor of a simple one wrapping a buffer, injectionBuffer(ByteBuffer), throwing an exception in case the remaining bytes are not enough for the requested length (of course, the users could either provide their own implementation of Binary, or they could use the pool we will be creating soon under contrib ;) ) )

I'm fine with this slight change - let me know what you think about this one.

cc @yurishkuro ;)

@carlosalberto
Copy link
Collaborator Author

@CodingFabian Hey, thanks for the answer :)

So I'd personally prefer to break the BINARY format here - but, if you think it's better (for you and your users) to add this new iteration under a new field, and simply deprecate the current one, let us know ;)

@CodingFabian
Copy link

as long its documented, I would guess the few users using it would find the doc.

@hypnoce
Copy link
Contributor

hypnoce commented Jun 8, 2018

@carlosalberto
This change looks nice to me :)

Do you think it's a good thing to document thrown exceptions or it's implementation dependant ?

Thanks !

@carlosalberto
Copy link
Collaborator Author

@hypnoce Oh, definitely we should document the exceptions (in the BinaryAdapters methods in this case).

Will update the PR in the next few days. Thanks for the feedback!

@tedsuo
Copy link
Member

tedsuo commented Jun 14, 2018

There's still room to change this once it's merged into the release candidate branch. But I would like to get feedback from @felixbarny and @raphw before doing so, to make sure their concerns have been addressed. Maybe a call in CET time would be quicker?

@raphw
Copy link

raphw commented Jun 17, 2018

I am currently on vacation and cannot follow this up but my concerns are the same as I initially mentioned. The current API would require to manage byte buffer pools both by the tracer and the user of a tracer. Also, the buffers must be sized by the required bytes for the trace information what makes reuse difficult.

@carlosalberto
Copy link
Collaborator Author

Hey @raphw

The current API would require to manage byte buffer pools both by the tracer and the user of a tracer. Also, the buffers must be sized by the required bytes for the trace information what makes reuse difficult.

The current API (which simply uses a ByteBuffer) is not much better than PR. Also, few applications simply require this (like Cassandra on the server side, which receives/gets a ByteBuffer as a binary payload).

But as Ted mentioned, I think it would be nice to have an actual call to talk about a binary, stream based, more complex format (separated, such as BINARY_STREAM) - whenever you are back from holidays, that is ;)

Simply pass the length to injectionBuffer() directly,
and refactor BinaryAdapters to wrap a ByteBuffer
for injection (instead of creating a new one).
@carlosalberto
Copy link
Collaborator Author

@hypnoce Hey, I have updated the PR to remove setInjectionBufferLength(). Let me know ;)

@hypnoce
Copy link
Contributor

hypnoce commented Jun 23, 2018

Hey @carlosalberto the changes look good to me. Thanks !

@carlosalberto
Copy link
Collaborator Author

Hey @raphw @felixbarny before going ahead and merge this PR, we would like do cover this issue/PR up in the next Cross-Language group call on July 13th (next week).

Observe the initial plan is to have this as the simple way to do binary propagation, but we would like to get some feedback (part of that is to work on some more advanced BINARY_STREAMING format). Let me know if you can make it to the call ;)

@carlosalberto
Copy link
Collaborator Author

So I think we will be merging this onto the v0.32.0 branch next week.

@raphw if you are still around, would be nice to schedule a Cross-Language call to talk about a more advanced BINARY_STREAM format (which most of us can't properly design/test as we are not experts in the area 😞 ) If not, I will create an Issue, to have it a reminder of this potential format.

@CodingFabian Final take on whether we could replace the existing BINARY format with this one, or should we add it as a new one, to not break code? ;)

@CodingFabian
Copy link

@carlosalberto i am fine with breaking as we did not implement this anyway. Thats my vendor perspective.
My user perspective is that this is still probably not usable.

@carlosalberto
Copy link
Collaborator Author

Hey @CodingFabian thanks for the answer.

i am fine with breaking as we did not implement this anyway
My user perspective is that this is still probably not usable

You mean the current or this proposed one? ;)

@carlosalberto
Copy link
Collaborator Author

Hey all,

Trying to get revive this PR - so essentially this approach is simply enough, yet handles an important case (telling the user in advance the required size of the buffer).

In addition, I'd like to address that while prototyping Cassandra server side support, I saw the payload is received as a straight ByteBuffer (https://github.com/carlosalberto/java-cassandra-server/), and while prototyping Hadoop integration (https://github.com/carlosalberto/hadoop/tree/ot_initial_integration), I realized that in such case it may be helpful (on top of Protocol Buffers).

The arguments in favor we had before were: keeping it as a simply, yet more realistic approach to the one we have now; and against it, probably the need of a more advanced format (which could be addressed by a 'streaming' one in the future).

Thoughts?

@carlosalberto
Copy link
Collaborator Author

carlosalberto commented Oct 16, 2018

Hey @CodingFabian

In case we decide to merge this PR for doing a RC to test out a few new features, I'm wondering if Instana needs this new format to be exposed with a different name, in order to not break things for your Java client? Let me know ;)

@carlosalberto
Copy link
Collaborator Author

Hey all,

I'd like to merge this PR by tomorrow, in order to include it in an incoming RC - so feel free to provide any feedback in case you think it needs tuning or shouldn't be merged even ;)

(And my understanding, upon re-reading the conversation, is that it's fine to break Instana' BINARY tracer support ;) )

@carlosalberto carlosalberto merged commit 9202384 into opentracing:v0.32.0 Oct 19, 2018
@carlosalberto
Copy link
Collaborator Author

Merged this PR - feel free to open an issue if you feel something needs tuning (I will be putting an eye on that, and will update accordingly)

Thanks all for your feedback!

* When Binary is defined as inbound, extractionBuffer() will be called to retrieve the ByteBuffer
* containing the data used for SpanContext extraction.
*
* When Binary is defined as outbound, setInjectBufferLength() will be called in order to hint

Choose a reason for hiding this comment

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

There is no setInjectBufferLenght() method defined in the interface, so it seems to me the documentation does not match the implementation here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, definitely. Will update, thanks for the observation.

carlosalberto added a commit that referenced this pull request Mar 25, 2019
* Deprecate the StringTag.set() overload taking a StringTag. (#262)
* Implement Trace Identifiers. (#280)
* Bump JaCoCo to use a Java 10 friendly version (#306)
* Remove finish span on close (#301)
  * Deprecate finishSpanOnClose on activation.
  * Add ScopeManager.activeSpan() and Tracer.activateSpan().
  * Clarify the API changes and deprecations.
  * Add an error reporting sample to opentracing-testbed.
* Simple layer on top of ByteBuffer for BINARY format. (#276)
* Add generic typed setTag/withTag (#311)
* Allow injecting into maps of type Map<String,Object> (#310)
* Add simple registerIfAbsent to global tracer (#289)
* Split Inject and Extract Builtin interfaces (#316)
* Deprecate ScopeManager.active() (#326)
* Make Tracer extends Closable. (#329)
* Do not make isRegistered() synchronized. (#333)
* Deprecate AutoFinishScopeManager (#335)
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants