Skip to content
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

Closed
wants to merge 6 commits into from

Conversation

spockz
Copy link
Contributor

@spockz spockz commented Sep 22, 2016

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.

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.
Copy link
Contributor

@kevinoliver kevinoliver left a 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 =
Copy link
Contributor

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)

Copy link
Contributor Author

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.

Copy link
Contributor

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).

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, please no ().

Copy link
Contributor Author

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")
Copy link
Contributor

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 = ...

Copy link
Contributor Author

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"
Copy link
Contributor

Choose a reason for hiding this comment

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

omit "Register"?

Copy link
Contributor Author

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?

Copy link
Contributor

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(_) =>
Copy link
Contributor

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

Copy link
Contributor Author

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,
Copy link
Contributor

Choose a reason for hiding this comment

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

omit override.

Copy link
Contributor Author

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] = {
Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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

Copy link
Contributor Author

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 =
Copy link
Contributor

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 =
Copy link
Contributor

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]] =
Copy link
Contributor

Choose a reason for hiding this comment

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

I honestly don't think we still need this Req <: Request type-param. I think we've done this before to support something we called "rich http" (which is now just finagle-http). Please, correct me if I'm wrong. /cc @mosesn @dschobel @roanta

Copy link
Contributor

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.

Copy link
Contributor Author

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
@taylorleese taylorleese changed the title Added enableHttpStats config to Http.[Server|Client]. Added withHttpStats config to Http.[Server|Client]. Sep 25, 2016
@@ -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>**
Copy link
Contributor

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.
Copy link
Contributor

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/

Copy link
Contributor Author

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto

Copy link
Contributor Author

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 =
Copy link
Contributor

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

Copy link
Contributor Author

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 =
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Contributor

@kevinoliver kevinoliver left a 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.

@spockz
Copy link
Contributor Author

spockz commented Sep 26, 2016

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 withHttpStats and not when newing the StatsFilter manually.

@@ -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.
Copy link
Contributor

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.

Copy link
Contributor

@kevinoliver kevinoliver left a 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.
Copy link
Contributor

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.
Copy link
Contributor

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] {
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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].

Copy link
Contributor Author

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]] =
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@codecov-io
Copy link

Current coverage is 68.24% (diff: 75.00%)

Merging #557 into develop will increase coverage by 1.06%

@@            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          

Powered by Codecov. Last update 00dc4cc...dae164b

@spockz
Copy link
Contributor Author

spockz commented Sep 27, 2016

Should we scope all the stats to start with 'http'? Otherwise we clutter the stats a bit, this will give nice grouping.

@vkostyukov
Copy link
Contributor

@spockz I don't know about scoping. On one hand, it's reasonable and that's what we do for mux (having a mux scope). On the other hand, we don't scope for HTTP and we already have some HTTP-specific metrics. So I prefer us to be consistent and either

  1. Do not scope new metrics
  2. Scope new metrics and move old metrics under http/

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.

@kevinoliver
Copy link
Contributor

@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.

@spockz
Copy link
Contributor Author

spockz commented Sep 28, 2016

I went the route @kevinoliver mentioned. The http.filter.StatsFilter itself doesn't add the http scope but the module does.

* Add http scope for StatsFilter added through module;
* Just Request
@kevinoliver
Copy link
Contributor

lgtm!

@mosesn mosesn added Ship It and removed Ship It labels Oct 3, 2016
@mosesn mosesn added the Ship It label Oct 3, 2016
@spockz
Copy link
Contributor Author

spockz commented Oct 7, 2016

@kevinoliver, do you have any news on this?

@mosesn
Copy link
Contributor

mosesn commented Oct 7, 2016

@spockz we're still working on merging it internally. We're hoping to get it in tomorrow. Thanks for checking in!

@jcrossley
Copy link
Contributor

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!

@jcrossley
Copy link
Contributor

All done, @spockz ! This has been merged internally and will go out with the next release :)

@spockz
Copy link
Contributor Author

spockz commented Oct 7, 2016

Great, Thank you @jcrossley!

@mosesn
Copy link
Contributor

mosesn commented Oct 11, 2016

made it to develop here: 2b65e41. thanks for the PR!

@mosesn mosesn closed this Oct 11, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

None yet

6 participants