New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added withHttpStats
config to Http.[Server|Client].
#557
Conversation
Problem com.twitter.finagle.http.filter.StatsFilter Is missing a corresponding module so it cannot be conveniently added to a Stack. Solution Added the module, added the `enableHttpStats` methods to Client and Server. Result It is now possible to effortlessly enable Http specific statistics on both http servers and clients.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Only a few small changes requested.
1 other thing we've tried to get better about is documenting metrics. Can you cover these in the HTTP section of Metrics.rst
?
/** | ||
* Enable the collection of HTTP specific metrics. See [[http.filter.StatsFilter]]. | ||
*/ | ||
def enableHttpStats(): Client = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did you consider withHttpStats
as a name? cc @vkostyukov (here and below)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. I choose enable because there are no arguments passed in. Just like noFailFast.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is noFailFast
on Client/Server
(it's a client builder thing). We use with
-prefixed methods here instead. And it's totally fine to have with
-API w/o an argument (we do that all the time). See Design Principles for the with
-API (item number 2).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, please no ()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
import com.twitter.util._ | ||
|
||
object StatsFilter { | ||
val role = Stack.Role("HttpStatsFilter") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i know we haven't always done a good job in the past, but i'd like to have public methods and members have type annotations.
val role: Stack.Role = ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
def module[Req <: Request]: Stackable[ServiceFactory[Req, Response]] = | ||
new Stack.Module1[param.Stats, ServiceFactory[Req, Response]] { | ||
val role = StatsFilter.role | ||
val description = "Register HTTP Stats" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
omit "Register"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are other descriptions of modules containing verbs. Why should it be gone here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess we aren't consistent. lets leave as is.
@@ -29,7 +49,7 @@ class StatsFilter[REQUEST <: Request](stats: StatsReceiver) | |||
future respond { | |||
case Return(response) => | |||
count(elapsed(), response) | |||
case Throw(_) => | |||
case Throw(_) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets leave these hashrockets unaligned
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
val role = StatsFilter.role | ||
val description = "Register HTTP Stats" | ||
|
||
override def make(statsParam: param.Stats, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
omit override
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
val description = "Register HTTP Stats" | ||
|
||
override def make(statsParam: param.Stats, | ||
next: ServiceFactory[Req, Response]): ServiceFactory[Req, Response] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just two spaces please.
|
||
override def make(statsParam: param.Stats, | ||
next: ServiceFactory[Req, Response]): ServiceFactory[Req, Response] = { | ||
if (statsParam.statsReceiver.isNull) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we typically drop braces for single exprs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
* Enable the collection of HTTP specific metrics. See [[http.filter.StatsFilter]]. | ||
*/ | ||
def enableHttpStats(): Server = | ||
withStack(http.filter.StatsFilter.module +: stack) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want to have non-idempotent configuration methods on StackClients. How about instead inserting a nop "http stats" module into the Http stack and then replacing it here?
It might also be useful to look at our design principles for configuration methods for StackClients.
http://twitter.github.io/finagle/guide/Configuration.html#design-principles
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I'll also have a look at those principles.
/** | ||
* Enable the collection of HTTP specific metrics. See [[http.filter.StatsFilter]]. | ||
*/ | ||
def enableHttpStats(): Client = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is noFailFast
on Client/Server
(it's a client builder thing). We use with
-prefixed methods here instead. And it's totally fine to have with
-API w/o an argument (we do that all the time). See Design Principles for the with
-API (item number 2).
/** | ||
* Enable the collection of HTTP specific metrics. See [[http.filter.StatsFilter]]. | ||
*/ | ||
def enableHttpStats(): Client = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, please no ()
.
object StatsFilter { | ||
val role = Stack.Role("HttpStatsFilter") | ||
|
||
def module[Req <: Request]: Stackable[ServiceFactory[Req, Response]] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vkostyukov I think you're right we don't need it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've put the type-param in there because the filter itself has it as well and limiting it in the module seemed strange. Additionally, our applications use types that inherit from RequestProxy
so we actually like the presence of the type-param in the filter and think the api would be less rich and flexible without it.
* enableHttpStats => withHttpStats * withHttpStats is now idempotent * API fixes
enableHttpStats
config to Http.[Server|Client].withHttpStats
config to Http.[Server|Client].
@@ -195,6 +195,21 @@ These stats pertain to the HTTP protocol. | |||
A counter of the number of non-retryable HTTP 503 responses the Http server returns. Those | |||
responses are not automatically retried. | |||
|
|||
**status/<statusCode>** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets add a sub heading explaining that they come from this Filter.
count as 5XX for this counter. | ||
|
||
**time/<statusCode>** | ||
Metric on duration per Http status code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for consistency with the rest of the file:
s/Metric/A histogram/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Metric on duration per Http status code. | ||
|
||
**time/<statusCategory>** | ||
Metric on duration per Http status code category. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -455,6 +455,11 @@ object Stack { | |||
Node(this, (prms, next) => Leaf(this, make(next.make(prms))), next) | |||
} | |||
|
|||
class NoOpModule[T](val role: Role, val description: String) extends Module0[T] { | |||
override def make(next: T): T = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we omit override
for abstract methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/** | ||
* Enable the collection of HTTP specific metrics. See [[http.filter.StatsFilter]]. | ||
*/ | ||
def withHttpStats: Client = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets add a basic unit test that verifies these work as expected. something like a request populates at least one of the stats. same for the server side.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good. a few small changes plus a test.
So, I've added a test-case. It also shows that the stats are directly under the client-label. Should we scope it to http? This would probably be a backwards incompatible change, unless we make it so that it is only prefixed with http when using |
@@ -204,10 +208,10 @@ These stats pertain to the HTTP protocol. | |||
count as 5XX for this counter. | |||
|
|||
**time/<statusCode>** | |||
Metric on duration per Http status code. | |||
A histogram on duration per Http status code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry, should've noticed previously, please add that these two histograms are in milliseconds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one small request, and then lgtm.
count as 5XX for this counter. | ||
|
||
**time/<statusCode>** | ||
A histogram on duration per Http status code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, capitalize HTTP.
A histogram on duration per Http status code. | ||
|
||
**time/<statusCategory>** | ||
A histogram on duration per Http status code category. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, capitalize HTTP.
@@ -455,6 +455,11 @@ object Stack { | |||
Node(this, (prms, next) => Leaf(this, make(next.make(prms))), next) | |||
} | |||
|
|||
class NoOpModule[T](val role: Role, val description: String) extends Module0[T] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can avoid defining a new type by just passing an identity
function as a stack module - it should be implicitly converted. We do that in a bunch of places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Identity is only used in places where stack.replace()
is used, not with prepend()
, there is no prepend that takes both a role and a function. How do you recommend to do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should be fine. There is an implicit conversion from A => A
function to Stackable[A]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not seeing it. How would we assign a role to the identity function?
val role: Stack.Role = Stack.Role("HttpStatsFilter") | ||
val description: String = "HTTP Stats" | ||
|
||
def module[Req <: Request]: Stackable[ServiceFactory[Req, Response]] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still don't think you need this type-param.
Please, note that it has nothing to do with your custom HTTP types (either for request or response) since at that point they should be lifted to something Finagle can work with (i.e., c.t.f.http.Request
and c.t.f.http.Response
).
By the same reason, StatsFilter
doesn't need a type param Req
, but we can't remove it yet (an API break) so let's keep it (filter) as is, but remove the type-param on a module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I'll remove it.
Current coverage is 68.24% (diff: 75.00%)@@ develop #557 diff @@
==========================================
Files 566 592 +26
Lines 20506 21172 +666
Methods 18988 19568 +580
Messages 0 0
Branches 1335 1444 +109
==========================================
+ Hits 13775 14448 +673
+ Misses 6731 6724 -7
Partials 0 0
|
Should we scope all the stats to start with 'http'? Otherwise we clutter the stats a bit, this will give nice grouping. |
@spockz I don't know about scoping. On one hand, it's reasonable and that's what we do for
It's quite clear that it's hard to go the second road here since it breaks the API (will likely break both internal and external dashboards). So I vote for 1. |
@vkostyukov "It's quite clear that it's hard to go the second road here since it breaks the API (will likely break both internal and external dashboards). So I vote for 1." This could be done for only users of the module which is new and therefore not a breaking change. I kinda like the scoping under http, but don't feel strongly. |
I went the route @kevinoliver mentioned. The |
* Add http scope for StatsFilter added through module; * Just Request
lgtm! |
@kevinoliver, do you have any news on this? |
@spockz we're still working on merging it internally. We're hoping to get it in tomorrow. Thanks for checking in! |
Hey @spockz ! I tried to merge in your request but ran into a couple small issues. Fixing those up now and will have it in by the end of the day, thanks! |
All done, @spockz ! This has been merged internally and will go out with the next release :) |
Great, Thank you @jcrossley! |
made it to develop here: 2b65e41. thanks for the PR! |
Problem
com.twitter.finagle.http.filter.StatsFilter Is missing a corresponding module
so it cannot be conveniently added to a Stack.
Solution
Added the module, added the
withHttpStats
methods to Client and Server.Result
It is now possible to effortlessly enable Http specific statistics on both
http servers and clients.