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

Support time-series DB using Kamon Tag #3343

Merged
merged 19 commits into from
Mar 1, 2018
Merged

Support time-series DB using Kamon Tag #3343

merged 19 commits into from
Mar 1, 2018

Conversation

upgle
Copy link
Member

@upgle upgle commented Feb 24, 2018

This PR is for supporting time-series database using Kamon Tag like OpenTSDB, Datadog.

and I also added granular metric again which was removed from #3284 PR. (but it is optional, default is False) because we need this feature for tracking activities per namespace, and already have built up enough infrastructure.

Reference

http://opentsdb.net/docs/build/html/user_guide/writing.html

OpenTSDB handles things a bit differently by introducing the idea of 'tags'. Each time series still has a 'metric' name, but it's much more generic, something that can be shared by many unique time series. Instead, the uniqueness comes from a combination of tag key/value pairs that allows for flexible queries with very fast aggregations.

https://docs.datadoghq.com/agent/tagging/

Tagging is used throughout the Datadog product to make it easier to subset and query the machines and metrics that you have to monitor.

Metrics tested in real environment.

* It is tested on both OpenTSDB and Graphite

1. OpenTSDB (Tag based aggregation)

In OpenTSDB, you can use regexable aggregation query with tag only.
e.g. host=regexp(web[0-9]+.lax.mysite.com)

image

2. Graphite (Metric name based aggregation)

In Graphite, you can use regexable aggregation query with a metric name only.
e.g. invoker_containerStart_*

image

image

@csantanapr
Copy link
Member

If it’s optional and and is false by default I don’t see an issue to add in the grand scheme of things.
👍

Copy link
Contributor

@markusthoemmes markusthoemmes left a comment

Choose a reason for hiding this comment

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

Thanks for adding this! 🎉

Got a lengthy question on the API design here, let's see what you think.

.counter(token.toString)
.increment(1)
if (token.tags.nonEmpty) metrics.counter(token.toString, token.tags.get).increment(1)
else metrics.counter(token.toString).increment(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be:

token.tags match {
  case Some(tags) => metrics.counter(token.toString, token.tags).increment(1)
  case None => metrics.counter(token.toString).increment(1)
}

Same below.

Copy link
Contributor

Choose a reason for hiding this comment

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

Edit: Might be ignorable, see lengthy comment.

def INVOKER_CONTAINER_START(containerState: String) =
LogMarkerToken(invoker, s"container_start_${containerState}", count)
def INVOKER_KUBECTL_CMD(cmd: String) = LogMarkerToken(invoker, s"kubectl.$cmd", start)
def INVOKER_DOCKER_CMD(cmd: String) = LogMarkerToken(invoker, "docker", start, Map("cmd" → cmd))
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove all ASCII arrows and replace them with -> (memo to self: automate this check)

}

private object Emitter {
val timeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneId.of("UTC"))
}

case class LogMarkerToken(component: String, action: String, state: String) {
case class LogMarkerToken(component: String, action: String, state: String, tags: Option[Map[String, String]] = None) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I've been looking through all the PR, and it feels a bit unclear to me to grasp all the branches on when something will be logged/sent and how. Maybe we can simplify:

What do you think about creating

LogMarkerToken(component: String, action: String, state: String, macroTags: Map[String, String] = Map.empty, microTags: Map[String, String] = Map.empty)

Then we could have

override def toString = component + "_" + action + "_" + state
override def toStringWithTags = component + "_" + action + macroTags.mkString(".", ".", "") + "_" + state

And call them in the specific call-site accordingly:

def emitCounterMetric(token: LogMarkerToken): Unit = {
  if (TransactionId.metricsKamon) {
    if (TransactionId.metricsKamonTags) {
      metrics.counter(token.toString, token.macroTags ++ token.microTags).increment(1)
    } else {
      metrics.counter(token.toStringWithTags).increment(1)
    }
  }
}

We maybe don't even need the granular option then. If the backend supports tags, we're fine anyway, right? We just need to make sure we only log the macro tags if the backend does not support "true" tags.

WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I agree with this all and think it is a very clear solution.
I will modify the code, test it, and then commit it again.

Thank you for your kind review!

Copy link
Member Author

Choose a reason for hiding this comment

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

@markusthoemmes
I changed the code, but I'm worried also the scaleable issue that occurs when using a low-capacity and low-performance Kamon-backend that supports tags.

Should we make a granular option again?
What do you think..?

}

private object Emitter {
val timeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneId.of("UTC"))
}

case class LogMarkerToken(component: String, action: String, state: String) {
case class LogMarkerToken(component: String,
Copy link
Contributor

Choose a reason for hiding this comment

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

A ScalaDoc comment here would be great, stating that macroTags should be used for tags with a bounded number of permutations only while microTags can be used for whatever granularity you might need.

val token = LogMarkerToken("http", s"${m.toLowerCase}.${res.status.intValue}", LoggingMarkers.count)
val token = LogMarkerToken(
"http",
s"${m.toLowerCase}",
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't need string interpolation anymore. Just m.toLowerCase should suffice.

def INVOKER_CONTAINER_START(containerState: String, namespaceName: String, actionName: String) =
LogMarkerToken(
invoker,
s"containerStart",
Copy link
Contributor

Choose a reason for hiding this comment

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

No string interpolation needed. Just containerStart is fine.

Copy link
Member Author

Choose a reason for hiding this comment

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

Modified. thank you

@upgle
Copy link
Member Author

upgle commented Feb 26, 2018

@markusthoemmes could you please review again?

Copy link
Contributor

@markusthoemmes markusthoemmes left a comment

Choose a reason for hiding this comment

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

Some more nits. We shouldn't entangle the LogMarkerTokens with a global value.

@mhenke1 could you please review the change in INVOKER_CONTAINER_START?

It changes from container_start_cold to containerStart.cold

override def toString() = component + "_" + action + "_" + state

// folderLeft is used instead of mkString(".", ".", ""),
// because Map.empty.mkString returns "."
def toStringWithTags = component + "_" + action + getTags.values.foldLeft("")((a, v) => a + "." + v) + "_" + state
Copy link
Contributor

Choose a reason for hiding this comment

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

You can simplify to

def toStringWithTags(includeMicroTags: Boolean) = {
  val tags = if(includeMicroTags) macroTags ++ microTags else macroTags
  val tagString = tags.values.foldLeft("")(_ + "." + _) // graceful handling of an empty map
  component + "_" + action + tagString + "_" + state
}

Note: I'd remove the entanglement of this instance to some arbitrary global value and pass it in instead for better control (in tests for instance).

if (TransactionId.metricsKamonTags) {
metrics.counter(token.toString, token.getTags).increment(1)
} else {
metrics.counter(token.toStringWithTags).increment(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Changes to

metrics.counter(token.toStringWithTags(includeMicroTags = false)).increment(1)

.counter(token.toString)
.increment(1)
if (TransactionId.metricsKamonTags) {
metrics.counter(token.toString, token.getTags).increment(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Changes to

metrics.counter(token.toString, if(TransactionId.granularMetric) token.allTags else token.macroTags).increment(1)

// folderLeft is used instead of mkString(".", ".", ""),
// because Map.empty.mkString returns "."
def toStringWithTags = component + "_" + action + getTags.values.foldLeft("")((a, v) => a + "." + v) + "_" + state
def getTags = if (TransactionId.granularMetric) macroTags ++ microTags else macroTags
Copy link
Contributor

Choose a reason for hiding this comment

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

How about def allTags = macroTags ++ microTags and use it from the outside?

@cbickel cbickel removed their request for review February 26, 2018 07:50
@cbickel cbickel removed their assignment Feb 26, 2018
* @param component Component like invoker, controller, and docker. It is defined in LoggingMarkers.
* @param action Action of the component.
* @param state State of the action.
* @param macroTags macroTags should be used for tags with a bounded number of permutations.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add an example for such a bounded tag (e.g. http staus code)

Copy link
Contributor

@mhenke1 mhenke1 left a comment

Choose a reason for hiding this comment

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

lgtm (please see one comment regarding parameter explaination)

Copy link
Contributor

@markusthoemmes markusthoemmes left a comment

Choose a reason for hiding this comment

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

LGTM

@markusthoemmes
Copy link
Contributor

@upgle it's taking me so long because there is a test failure in RuncClient tests. It is currently not run by travis due to a bug (will be resolved shoon #3367). Can you please take a look?

@upgle
Copy link
Member Author

upgle commented Feb 28, 2018

@markusthoemmes has it been resolved?

@markusthoemmes
Copy link
Contributor

@upgle it's merged yep, you can now rebase and try if the test passes (i think it won't).

@markusthoemmes markusthoemmes added needs-review awaits-feedback A reviewer has asked a question and/or asked for a change. reviewed Review for this PR is finished. It is mergeable from a review's perspective. and removed needs-review labels Feb 28, 2018
@upgle
Copy link
Member Author

upgle commented Mar 1, 2018

The test is passed, and some test code has modified to use log parser which is changed the signature.
now if there are some tags, log parser for unit test uses LogMarkerToken as a hint to parsing.
please review again. @markusthoemmes

@upgle upgle changed the title Support time-series DB using Kamon Tag, and optional granular metric. Support time-series DB using Kamon Tag Mar 1, 2018
@upgle
Copy link
Member Author

upgle commented Mar 1, 2018

conflicts have been resolved.

@markusthoemmes
Copy link
Contributor

PG3 1939 🔵

@markusthoemmes markusthoemmes added pgapproved Pipeline has approved this change. and removed awaits-feedback A reviewer has asked a question and/or asked for a change. labels Mar 1, 2018
@markusthoemmes markusthoemmes merged commit 6f1a445 into apache:master Mar 1, 2018
BillZong pushed a commit to BillZong/openwhisk that referenced this pull request Nov 18, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pgapproved Pipeline has approved this change. reviewed Review for this PR is finished. It is mergeable from a review's perspective.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants