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

OpenWhisk User Events #4584

Merged
merged 74 commits into from
Sep 25, 2019
Merged
Show file tree
Hide file tree
Changes from 63 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
b8be6a3
Initial commit
chetanmeh Oct 25, 2018
79472f5
Initial commit - Gradle support
chetanmeh Oct 25, 2018
fd045a6
Initial commit - Compile with Scala
chetanmeh Oct 25, 2018
79874c9
Enable Kamon and package as application
chetanmeh Oct 25, 2018
2606557
Exclude dependencies which are not required
chetanmeh Oct 25, 2018
9f76657
Copy the minimum EventMessage model from OpenWhisk code
chetanmeh Oct 26, 2018
b581155
Initial working implementation
chetanmeh Oct 26, 2018
5b53b14
Default logging config
chetanmeh Oct 26, 2018
bd5198d
Lookup KAFKA_HOST from env
chetanmeh Oct 27, 2018
4ed0f20
Change launcher class post refactoring
chetanmeh Oct 27, 2018
05bba0a
Docker build
chetanmeh Oct 27, 2018
dc647f0
Comment to indicate the classes here are from OpenWhisk
chetanmeh Oct 27, 2018
43ccd3c
Add remade and makefile
chetanmeh Oct 29, 2018
196e17e
Update Readme
chetanmeh Oct 29, 2018
82d3e8a
Prepare application for publishing to a public repo
selfxp Dec 11, 2018
3448f7c
Add LICENSE file
selfxp Dec 11, 2018
cddb3a8
Update README to remove unnecessary information
selfxp Dec 11, 2018
6abf7b3
Add docker-compose for prometheus and grafana
chetanmeh Dec 7, 2018
8d54bdb
Add build targets and update readme
chetanmeh Dec 7, 2018
9e85892
Remove some of the contributing guidelines parts
selfxp Dec 12, 2018
2387fc9
Update docker image name
selfxp Dec 13, 2018
b8e5786
Enable Travis builds (#2)
chetanmeh Dec 13, 2018
65a00ef
Enable build scan and coverage (#3)
chetanmeh Dec 15, 2018
5adff69
Use akka http server and add health checks (#4)
chetanmeh Dec 18, 2018
2355539
Add additional tags to the existing metrics (#1)
selfxp Dec 20, 2018
b8f83e2
Use Prometheus Java Client (#5)
chetanmeh Jan 2, 2019
d926df7
Match metric names per convention (#9)
chetanmeh Jan 2, 2019
ba54fbf
Reenable Kamon PrometheusReporter for non action metrics (#12)
chetanmeh Jan 4, 2019
65dabf5
Support for pushing a docker image to a registry (#13)
selfxp Jan 4, 2019
355de66
Fix issue of not being able to execute docker_push.sh (#14)
selfxp Jan 7, 2019
685b3f4
Count successful invocations too (#16)
selfxp Jan 7, 2019
80d2238
Add default Grafana dashboard (#17)
selfxp Jan 15, 2019
7a7ab91
Filter actions by namespace in the Grafana dashboard (#20)
selfxp Jan 17, 2019
78231f7
Enable capturing jvm metrics (#18)
chetanmeh Jan 22, 2019
529d54d
Add Grafana demo dashboard example to README (#22)
selfxp Jan 22, 2019
6402e14
Readme docker compose (#23)
selfxp Jan 23, 2019
ab6e746
Update Grafana dashboard to include only active labels (#26)
selfxp Jan 28, 2019
1f6338c
Add kind and memory labels to activations_total metric (#27)
selfxp Jan 29, 2019
af51a02
Change duration calculation from histogram_quantile to sum/count prop…
selfxp Mar 6, 2019
b62584a
Add a new chart to the dashboard to display the waitTime (#30)
selfxp Mar 7, 2019
d7cf007
Add gauge for tracking memory consumption (#31)
selfxp Mar 22, 2019
870e6da
Update dashboard to allow stack selection (#32)
selfxp May 8, 2019
c7b558d
Add dashboard for global metrics (#35)
chetanmeh May 15, 2019
19037a7
Record Global metrics (#34)
chetanmeh May 15, 2019
156eb9f
Track user event consumer Kafka lag as metric (#36)
chetanmeh May 16, 2019
0ebf922
Bugfix: Labels names the same as values (#38)
selfxp May 29, 2019
394ad49
Add initiator namespace tag for all metrics (#37)
selfxp Jun 7, 2019
9b22c7c
Uses Duration type for time units in Activation (#43)
chetanmeh Jun 11, 2019
44451a3
Add initiator to the dashboard (#44)
selfxp Jun 21, 2019
bbea918
Emit metrics related to hitting user limits (#45)
chetanmeh Aug 15, 2019
956ed11
Move user-events to user-events folder
selfxp Aug 15, 2019
3dc4c06
Remove files from parent directory
selfxp Aug 15, 2019
6344437
Merge remote-tracking branch 'user-events/ow-prepare' into ow-monitoring
selfxp Aug 15, 2019
074d542
Add user-events as part of the core components
selfxp Aug 16, 2019
7030786
Change license agreements
selfxp Aug 16, 2019
77fa8dc
Update main class name reference
selfxp Aug 16, 2019
81b3668
Use the EventMessage class from the common module
selfxp Aug 23, 2019
8e4746b
Add license headers to conf files
selfxp Aug 26, 2019
a46c4b8
Remove trailing whitespaces
selfxp Aug 26, 2019
43a9e12
Fmt code
selfxp Aug 26, 2019
f859355
Build docker image
selfxp Aug 26, 2019
5b96f3e
Start container on the same docker-tools network
selfxp Aug 27, 2019
89bff99
Reconfigure logback
selfxp Aug 28, 2019
f04275a
Move specific config under the whisk namespace
selfxp Aug 29, 2019
4965397
Use already existing messageForCode def
selfxp Sep 17, 2019
a38c668
Use EntityPath(name).toFullyQualifiedEntityName instead of path split
selfxp Sep 17, 2019
cc6329d
Use field names defined in WhiskActivation
selfxp Sep 17, 2019
e8d9234
Add user-events reference in readme
selfxp Sep 17, 2019
fa27d59
Merge branch 'master' into ow-monitoring
selfxp Sep 18, 2019
8081a1f
Make group.id and auto.offset.reset configurable
selfxp Sep 18, 2019
ff9a141
Merge remote-tracking branch 'origin/ow-monitoring' into ow-monitoring
selfxp Sep 18, 2019
9876c4c
Define return type for public methods
selfxp Sep 20, 2019
38b3174
Use already defined constants
selfxp Sep 24, 2019
09bd41e
Remove duplicated annotation
selfxp Sep 24, 2019
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import scala.util.Try
import spray.json._
import org.apache.openwhisk.common.TransactionId
import org.apache.openwhisk.core.entity._
import scala.concurrent.duration._
import java.util.concurrent.TimeUnit

/** Basic trait for messages that are sent on a message bus connector. */
trait Message {
Expand Down Expand Up @@ -194,22 +196,53 @@ object EventMessageBody extends DefaultJsonProtocol {

case class Activation(name: String,
statusCode: Int,
duration: Long,
waitTime: Long,
initTime: Long,
duration: Duration,
waitTime: Duration,
initTime: Duration,
kind: String,
conductor: Boolean,
memory: Int,
causedBy: Option[String])
extends EventMessageBody {
val typeName = "Activation"
val typeName = Activation.typeName
override def serialize = toJson.compactPrint

def toJson = Activation.activationFormat.write(this)

def status: String = statusCode match {
Copy link
Contributor

Choose a reason for hiding this comment

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

ActivationResponse already has messageForCode() - can we use that instead?

Copy link
Member

Choose a reason for hiding this comment

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

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah makes sense, I'll use that

// Defined in ActivationResponse
case 0 => Activation.statusSuccess
case 1 => Activation.statusApplicationError
case 2 => Activation.statusDeveloperError
case 3 => Activation.statusInternalError
case x => x.toString
}

def isColdStart: Boolean = initTime != Duration.Zero
}

object Activation extends DefaultJsonProtocol {

val typeName = "Activation"
def parse(msg: String) = Try(activationFormat.read(msg.parseJson))

val statusSuccess = "success"
Copy link
Member

Choose a reason for hiding this comment

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

these would get removed as well if you reuse existing code to map status code to a string.

val statusApplicationError = "application_error"
val statusDeveloperError = "developer_error"
val statusInternalError = "internal_error"

private implicit val durationFormat = new RootJsonFormat[Duration] {
override def write(obj: Duration): JsValue = obj match {
case o if o.isFinite() => JsNumber(o.toMillis)
case _ => JsNumber.zero
}

override def read(json: JsValue): Duration = json match {
case JsNumber(n) if n <= 0 => Duration.Zero
Copy link
Member

Choose a reason for hiding this comment

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

fwiw this would suggest a bug.

case JsNumber(n) => toDuration(n.longValue())
Copy link
Member

Choose a reason for hiding this comment

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

nit; drop () in longValue() and in isFinite() above.

}
}

implicit val activationFormat =
jsonFormat(
Activation.apply _,
Expand All @@ -223,6 +256,15 @@ object Activation extends DefaultJsonProtocol {
"memory",
"causedBy")
Copy link
Member

Choose a reason for hiding this comment

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

all these field names are defined in WhiskActivation - if there are some not defined there, they should be - this keeps the list in one canonical place. While not related to this PR, we should fix this either here or independently.

Copy link
Contributor Author

@selfxp selfxp Sep 13, 2019

Choose a reason for hiding this comment

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

👍 I'll create an issue for this


/**
* Extract namespace and action from name
* ex. whisk.system/apimgmt/createApi -> (whisk.system, apimgmt/createApi)
*/
def getNamespaceAndActionName(name: String): (String, String) = {
Copy link
Member

Choose a reason for hiding this comment

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

it may be helpful to use EntityPath(name).toFullyQualifiedEntityName instead as there could be an optional a leading slash... up to you.

val nameArr = name.split("/", 2)
(nameArr(0), nameArr(1))
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason not to split these in the Activation definition? (and avoid parsing these later etc)

/** Constructs an "Activation" event from a WhiskActivation */
def from(a: WhiskActivation): Try[Activation] = {
for {
Expand All @@ -234,9 +276,9 @@ object Activation extends DefaultJsonProtocol {
Activation(
fqn,
a.response.statusCode,
a.duration.getOrElse(0),
a.annotations.getAs[Long](WhiskActivation.waitTimeAnnotation).getOrElse(0),
a.annotations.getAs[Long](WhiskActivation.initTimeAnnotation).getOrElse(0),
toDuration(a.duration.getOrElse(0)),
toDuration(a.annotations.getAs[Long](WhiskActivation.waitTimeAnnotation).getOrElse(0)),
toDuration(a.annotations.getAs[Long](WhiskActivation.initTimeAnnotation).getOrElse(0)),
kind,
a.annotations.getAs[Boolean](WhiskActivation.conductorAnnotation).getOrElse(false),
a.annotations
Expand All @@ -246,6 +288,8 @@ object Activation extends DefaultJsonProtocol {
a.annotations.getAs[String](WhiskActivation.causedByAnnotation).toOption)
}
}

def toDuration(milliseconds: Long) = new FiniteDuration(milliseconds, TimeUnit.MILLISECONDS)
}

case class Metric(metricName: String, metricValue: Long) extends EventMessageBody {
Expand All @@ -255,6 +299,7 @@ case class Metric(metricName: String, metricValue: Long) extends EventMessageBod
}

object Metric extends DefaultJsonProtocol {
val typeName = "Metric"
chetanmeh marked this conversation as resolved.
Show resolved Hide resolved
def parse(msg: String) = Try(metricFormat.read(msg.parseJson))
implicit val metricFormat = jsonFormat(Metric.apply _, "metricName", "metricValue")
}
Expand All @@ -280,5 +325,5 @@ object EventMessage extends DefaultJsonProtocol {
}
}

def parse(msg: String) = format.read(msg.parseJson)
def parse(msg: String) = Try(format.read(msg.parseJson))
}
5 changes: 5 additions & 0 deletions core/monitoring/user-events/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*
!transformEnvironment.sh
!init.sh
!build/distributions
!Dockerfile
34 changes: 34 additions & 0 deletions core/monitoring/user-events/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

FROM scala

ENV UID=1001 \
NOT_ROOT_USER=owuser

# Copy app jars
ADD build/distributions/user-events.tar /

COPY init.sh /
RUN chmod +x init.sh

RUN adduser -D -u ${UID} -h /home/${NOT_ROOT_USER} -s /bin/bash ${NOT_ROOT_USER}
USER ${NOT_ROOT_USER}

# Prometheus port
EXPOSE 9095
CMD ["./init.sh", "0"]
55 changes: 55 additions & 0 deletions core/monitoring/user-events/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!--
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
-->

# ![OpenWhisk User Events](https://raw.githubusercontent.com/apache/openwhisk/master/core/monitoring/user-events/images/demo_landing.png)

# OpenWhisk User Events

This service connects to `events` topic and publishes the events to various services like Prometheus, Datadog etc via Kamon. Refer to [user specific metrics][1] on how to enable them.
Copy link
Member

Choose a reason for hiding this comment

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

I suggest to extend https://github.com/apache/openwhisk/blob/master/docs/metrics.md#user-specific-metrics to point to the metrics emitter provided by this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 yes good idea, adding a reference



## Local Run
>First configure and run `openwhisk docker-compose` that can be found in the [openwhisk-tools][2] project.

- Start service inside the cluster (on the same docker-compose network: `openwhisk_default`)
- The service will be available on port `9095`
- The endpoint for exposing the metrics for Prometheus can be found on `/metrics`.

## Usage

The service needs the following env variables to be set

- `KAFKA_HOSTS` - For local env it can be set to `172.17.0.1:9093`. When using [OpenWhisk Devtools][2] based setup use `kafka`

Integrations
------------

#### Prometheus
The docker container would run the service and expose the metrics in format required by [Prometheus][3] at `9095` port

#### Grafana
The `Openwhisk - Action Performance Metrics` Grafana[4] dashboard is available on localhost port `3000` at this address:
http://localhost:3000/d/Oew1lvymk/openwhisk-action-performance-metrics

The latest version of the dashboard can be found in the "compose/dashboard/openwhisk_events.json"

[1]: https://github.com/apache/incubator-openwhisk/blob/master/docs/metrics.md#user-specific-metrics
[2]: https://github.com/apache/incubator-openwhisk-devtools/tree/master/docker-compose
[3]: https://hub.docker.com/r/prom/prometheus/
[4]: https://hub.docker.com/r/grafana/grafana/
53 changes: 53 additions & 0 deletions core/monitoring/user-events/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

apply plugin: 'scala'
apply plugin: 'application'
apply plugin: 'org.scoverage'

ext.dockerImageName = 'user-events'
apply from: "../../../gradle/docker.gradle"
distDocker.dependsOn ':common:scala:distDocker', 'distTar'

project.archivesBaseName = "openwhisk-user-events"

repositories {
mavenCentral()
}

dependencies {
compile "org.scala-lang:scala-library:${gradle.scala.version}"
compile project(':common:scala')

compile 'com.typesafe.akka:akka-stream-kafka_2.12:0.22'

compile 'io.prometheus:simpleclient:0.6.0'
compile 'io.prometheus:simpleclient_common:0.6.0'

testCompile 'junit:junit:4.11'
testCompile 'org.scalatest:scalatest_2.12:3.0.1'
testCompile 'net.manub:scalatest-embedded-kafka_2.12:2.0.0'
testCompile 'com.typesafe.akka:akka-testkit_2.12:2.5.17'
testCompile 'com.typesafe.akka:akka-stream-testkit_2.12:2.5.17'
testCompile 'com.typesafe.akka:akka-http-testkit_2.12:10.1.5'
}

tasks.withType(ScalaCompile) {
scalaCompileOptions.additionalParameters = gradle.scala.compileFlags
}

mainClassName = "org.apache.openwhisk.core.monitoring.metrics.Main"
Loading