-
Notifications
You must be signed in to change notification settings - Fork 283
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
Lwcapi merge #459
Lwcapi merge #459
Changes from 162 commits
9d83204
cb323a0
36f71ef
d909eac
343bb45
500d3ec
5f29867
d6b951b
e3cedde
f8378e1
5722152
249a2b7
5728a06
c8f098f
04c112a
40fc240
cb98108
830d336
2f79b95
dbced2f
cc70713
a6346d0
e7f601b
f30e71b
0a898e6
d55e729
7867056
fd7cfcb
1adde74
db46730
56026ca
0c3f7e0
d47ab77
bd68086
76de29f
a24ac56
4006d01
f9863f2
00b6331
dfee9d0
a2670ea
8fbbfe0
c21038e
5a01bc6
4ce3a75
c503f15
12c57de
9a332dc
0ce9371
b790ade
eb47491
d33059f
b9edbfa
6ef570a
f876900
f980d15
d99c8eb
4575013
a6bcae2
1ec3326
9668e50
ad913ed
9a91d2d
20e5406
515d099
dc3a5ba
49452ac
6a48fc3
62ccfa6
80f6f0e
c555ecc
0548968
d0d2516
f102e9f
efff22f
a6aa0db
d400404
288529d
9495592
8180f3c
c0d7619
9a3504f
887722b
93008d8
bb529ad
bab4580
d54013f
715d6d5
7323f7d
12a5823
d143130
44e3e5d
99db2e7
19723aa
c98c065
98a4b8e
b955f4d
102e396
8edee5d
972eb08
da1265f
b06f5a9
383a011
b996a64
89426be
4c4dade
f7f3d27
8fd4606
dcb746f
d74e756
f0440f3
7873890
343f2d8
5842ba4
bc7bf1e
4079693
8b7306e
e7f9127
666af9f
16503d3
42c3dbe
c746c4d
bd52ffc
94116ca
aa22484
fe9488b
b9c9237
a59207d
fffc5a1
4a37938
b133e01
0452db7
1363d6d
5623898
cd58f23
f56b826
40c7f1d
4820b5d
8943114
9f0f420
557db2a
f47bd7c
dc7bf00
ac8616e
9e711a4
d71f044
ecdba60
f152279
8590261
d992766
b41b5ea
9e65284
b4d221b
ff6d2a9
0b5ac75
3e1fb02
0dd36e2
50cad0c
56ceff6
6c33206
d61dcd4
0292985
673b8f0
1a7db39
10a790c
c2bed30
f6eb7e9
001d936
ce4eab0
9a0e7b5
4b4f6a8
153e49f
c0069f6
afb28ae
b07299a
143dc72
6e9a4fb
b4e9a2b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,5 @@ cache: | |
directories: | ||
- $HOME/.sbt | ||
- $HOME/.ivy2 | ||
services: | ||
- redis-server |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
atlas { | ||
lwcapi { | ||
register = { | ||
default-frequency = 60000 | ||
} | ||
|
||
redis = { | ||
host = "localhost" | ||
port = 6379 | ||
ttl = 300000 // in milliseconds, default 5 minutes | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above comment on duration format. |
||
prefix = "" // default empty (could be omitted) | ||
} | ||
} | ||
|
||
akka { | ||
actors = ${?atlas.akka.actors} [ | ||
{ | ||
name = "lwc.expressiondb" | ||
class = "com.netflix.atlas.lwcapi.ExpressionDatabaseActor" | ||
}, | ||
{ | ||
name = "lwc.subscribe" | ||
class = "com.netflix.atlas.lwcapi.SubscribeActor" | ||
}, | ||
{ | ||
name = "lwc.evaluate" | ||
class = "com.netflix.atlas.lwcapi.EvaluateActor" | ||
} | ||
] | ||
|
||
api-endpoints = ${?atlas.akka.api-endpoints} [ | ||
"com.netflix.atlas.lwcapi.EvaluateApi", | ||
"com.netflix.atlas.lwcapi.ExpressionApi", | ||
"com.netflix.atlas.lwcapi.SubscribeApi", | ||
"com.netflix.atlas.lwcapi.StreamApi" | ||
] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/* | ||
* Copyright 2014-2016 Netflix, Inc. | ||
* | ||
* Licensed 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. | ||
*/ | ||
package com.netflix.atlas.lwcapi | ||
|
||
import com.netflix.atlas.config.ConfigManager | ||
import com.typesafe.config.Config | ||
|
||
object ApiSettings extends ApiSettings(ConfigManager.current) | ||
|
||
class ApiSettings(root: => Config) { | ||
private def config = root.getConfig("atlas.lwcapi") | ||
|
||
def defaultFrequency: Int = config.getInt("register.default-frequency") | ||
|
||
def redisHost: String = config.getString("redis.host") | ||
def redisPort: Int = config.getInt("redis.port") | ||
def redisTTL: Int = config.getInt("redis.ttl") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
def redisPrefix: String = { | ||
if (config.hasPath("redis.prefix")) { | ||
if (config.getString("redis.prefix").nonEmpty) | ||
config.getString("redis.prefix") + "." | ||
else | ||
"" | ||
} else "" | ||
} | ||
|
||
def redisPrefixFor(suffix: String): String = { | ||
s"$redisPrefix$suffix" | ||
} | ||
|
||
def stripRedisPrefix(s: String): String = { | ||
if (redisPrefix.isEmpty) s else s.stripPrefix(redisPrefix) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
* Copyright 2014-2016 Netflix, Inc. | ||
* | ||
* Licensed 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. | ||
*/ | ||
package com.netflix.atlas.lwcapi | ||
|
||
import javax.inject.Singleton | ||
|
||
import com.netflix.iep.service.{AbstractService, State} | ||
import com.typesafe.scalalogging.StrictLogging | ||
|
||
@Singleton | ||
class DatabaseService extends AbstractService with StrictLogging { | ||
@volatile private var started = false | ||
|
||
def setState(value: Boolean): Unit = started = value | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Locally to this class, it seems odd to have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought "state" was the state of the registration, while "started" really means "initial transfer from other instances via redis is complete" I can rename things to to make it clearer, and add commends, unless I really can / should touch "state" here. |
||
|
||
override def isHealthy: Boolean = state == State.RUNNING && started | ||
|
||
override def startImpl(): Unit = { | ||
logger.info("Starting Database service monitor") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The lifecycle manager will already have logs for calling the start/stop, e.g.: |
||
} | ||
|
||
override def stopImpl(): Unit = { | ||
logger.info("Stopping Database service monitor") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
* Copyright 2014-2016 Netflix, Inc. | ||
* | ||
* Licensed 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. | ||
*/ | ||
package com.netflix.atlas.lwcapi | ||
|
||
import javax.inject.Inject | ||
|
||
import akka.actor.{Actor, ActorLogging} | ||
import com.netflix.atlas.akka.DiagnosticMessage | ||
import com.netflix.atlas.lwcapi.StreamApi._ | ||
import com.netflix.spectator.api.Registry | ||
import spray.http.{HttpResponse, StatusCodes} | ||
|
||
class EvaluateActor @Inject() (registry: Registry, sm: SubscriptionManager) extends Actor with ActorLogging { | ||
import com.netflix.atlas.lwcapi.EvaluateApi._ | ||
|
||
private val evalsId = registry.createId("atlas.lwcapi.evaluate.count") | ||
private val itemsId = registry.createId("atlas.lwcapi.evaluate.itemCount") | ||
private val actorsId = registry.createId("atlas.lwcapi.evaluate.actorCount") | ||
private val uninterestingId = registry.createId("atlas.lwcapi.evaluate.uninterestingCount") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think in some other places we tend to call these "ignored". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will fix. |
||
|
||
def receive = { | ||
case EvaluateRequest(_, Nil) => | ||
DiagnosticMessage.sendError(sender(), StatusCodes.BadRequest, "empty expression payload") | ||
case EvaluateRequest(timestamp, items) => | ||
evaluate(timestamp, items) | ||
sender() ! HttpResponse(StatusCodes.OK) | ||
case _ => | ||
DiagnosticMessage.sendError(sender(), StatusCodes.BadRequest, "unknown payload") | ||
} | ||
|
||
private def evaluate(timestamp: Long, items: List[Item]): Unit = { | ||
registry.counter(evalsId).increment() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you aren't adding any additional tags, it would be better to cache the counter as the class member variable. It avoids looking up the id to find the counter. Lookups are fairly quick, but slower than just accessing a member variable. |
||
registry.counter(itemsId).increment(items.size) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note, that size on a list is linear. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since I iterate over the list, I could increment a counter and update the metric at the end. Do you think this is necessary? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not too worried about it in this particular case, just mentioning in case there is an easy way to avoid it. I have seen things like size calls become pretty expensive in the past and it is often assumed it will be a constant time operation. When we start running it under more load we can deal with it if it shows up on a profile. |
||
items.foreach { item => | ||
val actors = sm.actorsForExpression(item.id) | ||
if (actors.nonEmpty) { | ||
registry.counter(actorsId).increment(actors.size) | ||
val message = SSEMetric(timestamp, item) | ||
actors.foreach(actor => actor ! message) | ||
} else { | ||
registry.counter(uninterestingId).increment() | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* | ||
* Copyright 2014-2016 Netflix, Inc. | ||
* | ||
* Licensed 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. | ||
*/ | ||
package com.netflix.atlas.lwcapi | ||
|
||
import akka.actor.ActorRefFactory | ||
import com.netflix.atlas.akka.WebApi | ||
import com.netflix.atlas.json.{Json, JsonSupport} | ||
import com.netflix.atlas.lwcapi.StreamApi.SSEMessage | ||
import spray.routing.RequestContext | ||
|
||
class EvaluateApi(implicit val actorRefFactory: ActorRefFactory) extends WebApi { | ||
import EvaluateApi._ | ||
|
||
private val evaluateRef = actorRefFactory.actorSelection("/user/lwc.evaluate") | ||
|
||
def routes: RequestContext => Unit = { | ||
path("lwc" / "api" / "v1" / "evaluate") { | ||
post { ctx => handleReq(ctx) } | ||
} | ||
} | ||
|
||
private def handleReq(ctx: RequestContext): Unit = { | ||
val request = EvaluateRequest.fromJson(ctx.request.entity.asString) | ||
evaluateRef.tell(request, ctx.responder) | ||
} | ||
} | ||
|
||
object EvaluateApi { | ||
type TagMap = Map[String, String] | ||
|
||
case class Item(id: String, tags: TagMap, value: Double) extends JsonSupport | ||
|
||
case class EvaluateRequest(timestamp: Long, metrics: List[Item]) extends JsonSupport | ||
object EvaluateRequest { | ||
def fromJson(json: String) = Json.decode[EvaluateRequest](json) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* | ||
* Copyright 2014-2016 Netflix, Inc. | ||
* | ||
* Licensed 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. | ||
*/ | ||
package com.netflix.atlas.lwcapi | ||
|
||
import javax.inject.Inject | ||
|
||
import akka.actor.ActorRefFactory | ||
import com.netflix.atlas.akka.WebApi | ||
import com.netflix.atlas.json.{Json, JsonSupport} | ||
import com.netflix.spectator.api.Registry | ||
import com.typesafe.scalalogging.StrictLogging | ||
import spray.http.{HttpResponse, StatusCodes} | ||
import spray.routing.RequestContext | ||
|
||
case class ExpressionApi @Inject()(alertmap: ExpressionDatabase, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alert map name seems odd here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renaming issue from a long time ago. |
||
registry: Registry, | ||
implicit val actorRefFactory: ActorRefFactory) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer not to align with the opening paren, it creates a big gap and leads to unnecessary diffs with name changes. |
||
extends WebApi with StrictLogging { | ||
import ExpressionApi._ | ||
|
||
private val defaultURL = "http://..." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This appears to be unused. |
||
|
||
private val expressionFetchesId = registry.createId("atlas.lwcapi.expressions.fetches") | ||
private val expressionCount = registry.distributionSummary("atlas.lwcapi.expressions.count") | ||
|
||
def routes: RequestContext => Unit = { | ||
path("lwc" / "api" / "v1" / "expressions" / Segment) { (cluster) => | ||
get { ctx => handleReq(ctx, cluster) } | ||
} | ||
} | ||
|
||
private def handleReq(ctx: RequestContext, cluster: String): Unit = { | ||
val expressions = alertmap.expressionsForCluster(cluster) | ||
val json = Return(expressions).toJson | ||
ctx.responder ! HttpResponse(StatusCodes.OK, entity = json) | ||
registry.counter(expressionFetchesId.withTag("cluster", cluster)).increment() | ||
expressionCount.record(expressions.size) | ||
} | ||
} | ||
|
||
object ExpressionApi { | ||
case class Return(expressions: List[ExpressionWithFrequency]) extends JsonSupport | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright 2014-2016 Netflix, Inc. | ||
* | ||
* Licensed 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. | ||
*/ | ||
package com.netflix.atlas.lwcapi | ||
|
||
import com.netflix.atlas.core.model.Query | ||
import com.netflix.atlas.lwcapi.ExpressionSplitter.SplitResult | ||
|
||
abstract class ExpressionDatabase { | ||
def addExpr(expr: ExpressionWithFrequency, queries: Query): Boolean | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/queries/query/ |
||
def delExpr(id: String): Boolean | ||
def hasExpr(id: String): Boolean | ||
def expr(id: String): Option[ExpressionWithFrequency] | ||
def expressionsForCluster(cluster: String): List[ExpressionWithFrequency] | ||
} |
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.
Use duration format so it is easier to read and the unit is explicit.