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

Add Helper Function to Build classifierF (to pass to client and server's Middleware) #3167

Merged
merged 42 commits into from
Feb 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
12a277c
Add 'classifierFMethodWithOptionallyExcludedPath'.
kevinmeredith Feb 9, 2020
c13d63e
Add 'Arbitrary[UUID]'.
kevinmeredith Feb 9, 2020
39ef434
Add tests for 'classifierFMethodWithOptionallyExcludedPath'.
kevinmeredith Feb 9, 2020
494b7de
Run scalafmt for src and test.
kevinmeredith Feb 9, 2020
b955e82
Add classifier results to documentation.
kevinmeredith Feb 9, 2020
695d37f
Update docs to indicate both client and server's Metrics#apply are re…
kevinmeredith Feb 9, 2020
c9748b7
Update pattern match to explicitly be non-empty.
kevinmeredith Feb 9, 2020
6317e9b
Revert "Add 'Arbitrary[UUID]'."
kevinmeredith Feb 9, 2020
4329e58
Add Arbitrary[UUID] to PrometheusSpec.
kevinmeredith Feb 9, 2020
1c3a145
Remove dependency on Http4sDsl.
kevinmeredith Feb 10, 2020
9292527
Add function to extract path (List[String]) from Request.
kevinmeredith Feb 10, 2020
20248b2
Call function for extracting path list from Request.
kevinmeredith Feb 10, 2020
34cbd08
Run scalafmt on src/.
kevinmeredith Feb 10, 2020
0351700
Increase performance of 'requestToPathList'.
kevinmeredith Feb 11, 2020
8c09866
Add comment that the output will be lower-cased.
kevinmeredith Feb 11, 2020
f3693c5
Pass 'excludedValue' and 'pathSeparator' as configurable input.
kevinmeredith Feb 11, 2020
b9354e9
Update spec to include generators for 'excludedValue' and 'separator'.
kevinmeredith Feb 11, 2020
19a17d9
Run scalafmt for /src and /test.
kevinmeredith Feb 11, 2020
7fc1200
Call String#toLowerCase at end of function.
kevinmeredith Feb 11, 2020
3a0cfc7
Remove a 'toLowerCase' that I missed.
kevinmeredith Feb 11, 2020
badda65
Remove lower-casing.
kevinmeredith Feb 13, 2020
e6b26f5
Update spec to not expect lower-cased value.
kevinmeredith Feb 13, 2020
ae74598
Add default values.
kevinmeredith Feb 13, 2020
48c0661
Add note on performance in comment regarding Throwable's versus regex's.
kevinmeredith Feb 13, 2020
7819ac9
Move 'classifierFMethodWithOptionallyExcludedPath' from Prometheus to…
kevinmeredith Feb 13, 2020
ab1661d
'git mv' and re-name.
kevinmeredith Feb 13, 2020
0c6873a
Add 'specs2-scalacheck' dependency for test-only.
kevinmeredith Feb 14, 2020
f806124
Add specs2-scalacheck dependency in test for 'core'.
kevinmeredith Feb 14, 2020
8e2b022
Re-name class: PrometheusSpec -> MetricsOpsSpec.
kevinmeredith Feb 14, 2020
cb7c7c3
Update MetricsOpsSpec to be a specs2-scalacheck test.
kevinmeredith Feb 14, 2020
b9600af
Add Arbitrary[Method].
kevinmeredith Feb 14, 2020
c7df72c
Run scalafmt in src and test.
kevinmeredith Feb 14, 2020
e4fa24a
Merge branch 'series/0.20' into classifier-helper
kevinmeredith Feb 14, 2020
ff9cae6
Remove unnecessary 'specs2-scalacheck' dependency in test.
kevinmeredith Feb 16, 2020
f0423e1
Remove core's dependency on deleted specs2-scalacheck (in test).
kevinmeredith Feb 16, 2020
d79a173
Move MetricsOpsSpec to tests/.
kevinmeredith Feb 16, 2020
b9df51a
Update spec to extends Http4sSpec.
kevinmeredith Feb 16, 2020
fd90412
Remove unnecessary imports.
kevinmeredith Feb 16, 2020
59feb8b
Remove redundant Arbitrary[Method].
kevinmeredith Feb 16, 2020
6d2a699
Add trailing comma to build.sbt.
kevinmeredith Feb 16, 2020
434aba7
Add newline to Prometheus.
kevinmeredith Feb 16, 2020
ee7826d
Run scalafmt on prometheus-metrics.
kevinmeredith Feb 16, 2020
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
82 changes: 81 additions & 1 deletion core/src/main/scala/org/http4s/metrics/MetricsOps.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.http4s.metrics

import org.http4s.{Method, Status}
import cats.Foldable
import cats.implicits._
import org.http4s.{Method, Request, Status, Uri}

/**
* Describes an algebra capable of writing metrics to a metrics registry
Expand Down Expand Up @@ -57,6 +59,84 @@ trait MetricsOps[F[_]] {
classifier: Option[String]): F[Unit]
}

object MetricsOps {

/**
* Given an exclude function, return a 'classifier' function, i.e. for application in
* org.http4s.server/client.middleware.Metrics#apply.
*
* Let's say you want a classifier that excludes integers since your paths consist of:
* * GET /users/{integer} = GET_users_*
* * POST /users = POST_users
* * PUT /users/{integer} = PUT_users_*
* * DELETE /users/{integer} = DELETE_users_*
*
* In such a case, we could use:
*
* classifierFMethodWithOptionallyExcludedPath(
* exclude = { str: String => scala.util.Try(str.toInt).isSuccess },
* excludedValue = "*",
* intercalateValue = "_"
* )
*
*
* Chris Davenport notes the following on performance considerations of exclude's function value:
*
* > It's worth noting that this runs on every segment of a path. So note that if an intermediate Throwables with
* > Stack traces is known and discarded, there may be a performance penalty, such as the above example with Try(str.toInt).
* > I benchmarked some approaches and regex matches should generally be preferred over Throwable's
* > in this position.
*
* @param exclude For a given String, namely a path value, determine whether the value gets excluded.
* @param excludedValue Indicates the String value to be supplied for an excluded path's field.
* @param pathSeparator Value to use for separating the metrics fields' values
* @return Request[F] => Option[String]
*/
def classifierFMethodWithOptionallyExcludedPath[F[_]](
exclude: String => Boolean,
excludedValue: String = "*",
pathSeparator: String = "_"
): Request[F] => Option[String] = { request: Request[F] =>
val initial: String = request.method.name

val pathList: List[String] =
requestToPathList(request)

val minusExcluded: List[String] = pathList.map { value: String =>
if (exclude(value)) excludedValue else value
}

val result: String =
minusExcluded match {
case Nil => initial
case nonEmpty @ _ :: _ =>
initial + pathSeparator + Foldable[List]
.intercalate(nonEmpty, pathSeparator)
}

Some(result)
}

// The following was copied from
// https://github.com/http4s/http4s/blob/v0.20.17/dsl/src/main/scala/org/http4s/dsl/impl/Path.scala#L56-L64,
// and then modified.
private def requestToPathList[F[_]](request: Request[F]): List[String] = {
val str: String = request.pathInfo

if (str == "" || str == "/")
Nil
else {
val segments = str.split("/", -1)
// .head is safe because split always returns non-empty array
val segments0 = if (segments.head == "") segments.drop(1) else segments
val reversed: List[String] =
segments0.foldLeft[List[String]](Nil)((path, seg) => Uri.decode(seg) :: path)
reversed.reverse
}
}

}

/** Describes the type of abnormal termination*/
sealed trait TerminationType

Expand Down
79 changes: 79 additions & 0 deletions tests/src/test/scala/org/http4s/metrics/MetricsOpsSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.http4s.metrics

import cats.effect.IO
import cats.implicits._
import java.util.UUID
import org.http4s._
import org.scalacheck.{Arbitrary, Gen}
import MetricsOps.classifierFMethodWithOptionallyExcludedPath

object MetricsOpsSpec {

private implicit val arbUUID: Arbitrary[UUID] =
Arbitrary(Gen.uuid)
}

class MetricsOpsSpec extends Http4sSpec {

import MetricsOpsSpec.arbUUID

"classifierFMethodWithOptionallyExcludedPath" should {
"properly exclude UUIDs" in prop {
(method: Method, uuid: UUID, excludedValue: String, separator: String) =>
{
val request: Request[IO] = Request[IO](
method = method,
uri = Uri.unsafeFromString(s"/users/$uuid/comments")
)

val excludeUUIDs: String => Boolean = { str: String =>
Either
.catchOnly[IllegalArgumentException](UUID.fromString(str))
.isRight
}

val classifier: Request[IO] => Option[String] =
classifierFMethodWithOptionallyExcludedPath(
exclude = excludeUUIDs,
excludedValue = excludedValue,
pathSeparator = separator
)

val result: Option[String] =
classifier(request)

val expected: Option[String] =
Some(
method.name +
separator +
"users" +
separator +
excludedValue +
separator +
"comments"
)

result ==== expected
}
}
"return '$method' if the path is '/'" in prop { method: Method =>
val request: Request[IO] = Request[IO](
method = method,
uri = uri"""/"""
)

val classifier: Request[IO] => Option[String] =
classifierFMethodWithOptionallyExcludedPath(
_ => true,
"*",
"_"
)

val result: Option[String] =
classifier(request)

result ==== Some(method.name)
}
}

}