├── .gitignore
├── .travis.yml
├── README.md
├── _config.yml
├── alarm-common
├── pom.xml
└── src
│ └── main
│ └── scala
│ └── com
│ └── netease
│ └── spark
│ └── alarm
│ ├── AlarmConstants.scala
│ ├── Alarmist.scala
│ ├── AlertMessage.scala
│ ├── AlertResp.scala
│ ├── AlertType.scala
│ └── Components.scala
├── alarm-email
├── pom.xml
└── src
│ ├── main
│ └── scala
│ │ └── com
│ │ └── netease
│ │ └── spark
│ │ └── alarm
│ │ └── email
│ │ ├── EmailAlarmist.scala
│ │ └── package.scala
│ └── test
│ └── scala
│ └── com
│ └── netease
│ └── spark
│ └── alarm
│ └── email
│ └── EmailAlarmistTest.scala
├── alarm-sentry
├── pom.xml
└── src
│ ├── main
│ └── scala
│ │ └── com
│ │ └── netease
│ │ └── spark
│ │ └── alarm
│ │ └── sentry
│ │ ├── SentryAlarmist.scala
│ │ ├── SentryStatus.scala
│ │ ├── SentryType.scala
│ │ └── package.scala
│ └── test
│ └── scala
│ └── com
│ └── netease
│ └── spark
│ └── alarm
│ └── sentry
│ └── SentryAlarmistTest.scala
├── alarm-smilodon
├── pom.xml
└── src
│ ├── main
│ └── scala
│ │ └── com
│ │ └── netease
│ │ └── spark
│ │ └── alarm
│ │ └── smilodon
│ │ ├── SmilodonAlarmist.scala
│ │ ├── SmilodonAlertLevel.scala
│ │ ├── SmilodonAlertMessage.scala
│ │ ├── channel
│ │ ├── ChannelType.scala
│ │ └── SmilodonChannel.scala
│ │ ├── event
│ │ └── SmilodonEvent.scala
│ │ └── package.scala
│ └── test
│ └── scala
│ └── com
│ └── netease
│ └── spark
│ └── alarm
│ └── smilodon
│ └── SmilodonAlarmistTest.scala
├── pom.xml
└── streaming-alarmer
├── pom.xml
└── src
├── main
└── scala
│ └── com
│ └── netease
│ └── spark
│ └── alarm
│ └── alarmer
│ └── streaming
│ ├── EmailStreamingAlarmListener.scala
│ ├── SentryStreamingAlarmListener.scala
│ ├── SmilodonStreamingAlarmListener.scala
│ ├── StreamingAlarmListener.scala
│ └── package.scala
└── test
└── scala
├── com
└── netease
│ └── spark
│ └── alarm
│ └── alarmer
│ └── streaming
│ ├── EmailStreamingAlarmListenerTest.scala
│ ├── SmilodonStreamingAlarmListenerTest.scala
│ └── StreamingAlarmListenerTest.scala
└── org
└── apache
└── spark
└── streaming
└── AlarmTestInputStream.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | /alarm-common/alarm-common.iml
3 | /spark-alarm.iml
4 | /alarm-email/alarm-email.iml
5 | /alarm-sentry/alarm-sentry.iml
6 | /alarm-smilodon/alarm-smilodon.iml
7 | /.idea/
8 | .idea/dictionaries/
9 | .idea/encodings.xml
10 | .idea/misc.xml
11 | .idea/sbt.xml
12 | .idea/thriftCompiler.xml
13 | .idea/vcs.xml
14 | .idea/workspace.xml
15 | alarm-common/target/
16 | alarm-email/target/
17 | alarm-sentry/target/
18 | alarm-smilodon/target/
19 | /streaming-alarmer/streaming-alarmer.iml
20 | /streaming-alarmer/dependency-reduced-pom.xml
21 | /streaming-alarmer/target/
22 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 | scala:
3 | - 2.11.8
4 |
5 | cache:
6 | directories:
7 | - $HOME/.m2
8 | - ./build
9 |
10 | deploy:
11 | - provider: pages
12 | skip_cleanup: true
13 | github_token: $GITHUB_TOKEN
14 | email: yaooqinn@hotmail.com
15 | name: Kent Yao
16 | on:
17 | branch: master
18 |
19 | jobs:
20 | include:
21 | - stage: spark2.4
22 | language: scala
23 | script: mvn clean install -Pspark-2.4 -Dmaven.javadoc.skip=true -B -V
24 | - stage: spark2.3
25 | language: scala
26 | script: mvn clean install -Pspark-2.3 -Dmaven.javadoc.skip=true -B -V
27 | - stage: spark2.2
28 | language: scala
29 | script: mvn clean install -Pspark-2.2 -Dmaven.javadoc.skip=true -B -V
30 | - stage: spark2.1
31 | language: scala
32 | script: mvn clean install -Pspark-2.1 -Dmaven.javadoc.skip=true -B -V
33 |
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spark-Alarm [](https://travis-ci.com/yaooqinn/spark-alarm)[](http://hits.dwyl.io/yaooqinn/spark-alarm)
2 |
3 |
4 | ## 项目简介
5 |
6 | 提供一些基本的报警手段,并可以通过SparkListener实现对Spark内部执行逻辑进行监控报警
7 |
8 | | 报警模式 | 使用限制 | 简介 |
9 | | ------ | ------ | ------ |
10 | | 邮件 | 通用,无限制 | 通过 SMTP 协议发送告警 |
11 | | 哨兵 | 网易内部使用 | 通过 HTTP 协议发送告警|
12 | | Smilodon | 网易内部使用 | 通过 HTTP 协议发送告警 |
13 |
14 | ## 使用方法
15 |
16 | ### 编译
17 |
18 | ```bash
19 | # 克隆本项目
20 | git https://github.com/yaooqinn/spark-alarm.git
21 | # cd spark-alarm
22 | # mvn clean package
23 | ```
24 |
25 | 可以得到内置示例项目jar包:`streaming-alarmer/target/streaming-alarmer-1.0-SNAPSHOT.jar`,该构件实现了对Streaming程序"异常退出"和""任务堆积"等相关关键事件进行简单的告警服务
26 |
27 | ## 配置
28 |
29 | 配置工作分为三个过程:
30 | - 配置报警方式相关参数
31 | - 配置报警规则参数
32 | - 配置spark.extraListeners
33 | 所有的参数都完全等价Spark内置的各项参数,可以通过代码/--conf/spark-default.conf等方式加入
34 |
35 | ### 配置邮件报警
36 |
37 | | 配置 | 默认值 | 说明 |
38 | | ------ | ------ | ------ |
39 | | spark.alarm.email.smtp.host | <空值> | 发信邮件服务器的地址, 比如 smtp-mail.outlook.com |
40 | | spark.alarm.email.smtp.port | 465(ssl)/25(no ssl) | 发信邮件服务器的端口, 587|
41 | | spark.alarm.email.username | <空值> | 发信邮箱的用户名 |
42 | | spark.alarm.email.password | <空值> | 发信邮箱的密码 |
43 | | spark.alarm.email.ssl.on.connect | false | 是否ssl加密连接 |
44 | | spark.alarm.email.from | <空值> | 发信邮箱 |
45 | | spark.alarm.email.to | <空值> | 收信邮箱列表,用逗号分隔 |
46 |
47 | 参见: https://github.com/yaooqinn/spark-alarm/blob/master/alarm-email/src/test/scala/com/netease/spark/alarm/email/EmailAlarmistTest.scala
48 |
49 | ### 配置哨兵报警
50 |
51 | | 配置 | 默认值 | 说明 |
52 | | ------ | ------ | ------ |
53 | | spark.alarm.sentry.url | <空值> | 哨兵服务地址 |
54 | | spark.alarm.sentry.api.type | Stone | 哨兵服务通知到的终端类型 /** 泡泡 */ POPO, /** 易信 */YiXin, /** 短信 */ SMS, /** 语音 */ Voice, Stone, /** 邮件 */ Email|
55 | | spark.alarm.sentry.app.name | <空值> | 哨兵服务的应用名称,若没有可以去哨兵官网添加 |
56 | | spark.alarm.sentry.app.secret | <空值> | 哨兵服务应用对应的密钥 |
57 | | spark.alarm.sentry.to | <空值> | 收信邮箱、或者ID、电话列表,用逗号分隔,根据具体的终端类型决定 |
58 |
59 | 参见: alarm-sentry/src/test/scala/com/netease/spark/alarm/sentry/SentryAlarmistTest.scala
60 |
61 | ### 配置Smilodon报警
62 |
63 | | 配置 | 默认值 | 说明 |
64 | | ------ | ------ | ------ |
65 | | spark.alarm.smilodon.url | http://10.165.139.76:8765/v1/alerts | Smilodon服务地址 |
66 | | spark.alarm.smilodon.channels | POPO,STONE | 通知中心终端类型,目前只支持|
67 | | spark.alarm.smilodon.users | <空值> | 收信者的邮箱账号列表,逗号分隔 |
68 | | spark.alarm.smilodon.groups | <空值> | 暂时不用管 |
69 |
70 | 参见: alarm-smilodon/src/test/scala/com/netease/spark/alarm/smilodon/SmilodonAlarmistTest.scala
71 |
72 | 注意:Smilodon服务有白名单控制,所以需要用户Spark作业的Driver进程所在的服务器在该列表中,对于Cluster模式则需要所有的节点(如NodeManager)在该列表中
73 |
74 | ### 配置报警规则参数
75 |
76 | 该参数列表适用于本项目内置示例项目streaming-alarmer的报警规则,用户可以自己定义相关的SparkListener来定制报警器
77 |
78 | | 配置 | 默认值 | 说明 |
79 | | ------ | ------ | ------ |
80 | | spark.alarm.streaming.application.force.exit | false | 出现Application级别错误的时候,是否直接exit |
81 | | spark.alarm.streaming.batch.error.enable | true | 是否对Batch出错的信息进行告警 |
82 | | spark.alarm.streaming.job.error.enable | false | 是否对job出错的信息进行告警 |
83 | | spark.alarm.streaming.batch.delay.ratio | 1 | Schedule delay时间 / Processing 时间的比值,大于该值,则作为判定为需要告警的必要条件之一 |
84 | | spark.alarm.streaming.batch.process.time.threshold | 1s | 处理时间的阈值,实际处理时间大于该值的批次,才进行报警 |
85 |
86 | 参见:streaming-alarmer/src/test/scala/com/netease/spark/alarm/alarmer/streaming/EmailStreamingAlarmListenerTest.scala
87 |
88 |
89 | ### 配置spark.extraListeners
90 |
91 | 开启邮件报警,也可以设置其他实现,或者多个逗号分隔的报警实现,以及自己拓展的实现
92 | ```scala
93 | spark.extraListeners=com.netease.spark.alarm.alarmer.streaming.EmailStreamingAlarmListener
94 | ```
95 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-leap-day
2 |
--------------------------------------------------------------------------------
/alarm-common/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | spark-alarm
7 | com.netease.spark
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | alarm-common
13 | Spark Alarm Common
14 | jar
15 |
16 |
17 | org.scala-lang
18 | scala-library
19 |
20 |
21 |
22 |
23 | org.scalatest
24 | scalatest_${scala.bin.ver}
25 |
26 |
27 |
28 |
29 | target/scala-${scala.bin.ver}/classes
30 | target/scala-${scala.bin.ver}/test-classes
31 |
32 |
33 | ${project.basedir}/src/main/resources
34 |
35 |
36 |
37 |
38 | org.apache.maven.plugins
39 | maven-compiler-plugin
40 |
41 |
42 |
43 | net.alchim31.maven
44 | scala-maven-plugin
45 |
46 |
47 |
48 |
49 | org.apache.maven.plugins
50 | maven-surefire-plugin
51 |
52 |
53 |
54 | org.scalatest
55 | scalatest-maven-plugin
56 | 1.0
57 |
58 | ${project.build.directory}/surefire-reports
59 | .
60 | TestSuite.txt
61 |
62 | ${project.build.testOutputDirectory}/kyuubi-server-${project.version}.jar
63 |
64 |
65 | ${project.build.testOutputDirectory}/spark-warehouse
66 |
67 |
68 |
69 |
70 | test
71 |
72 | test
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/alarm-common/src/main/scala/com/netease/spark/alarm/AlarmConstants.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm
2 |
3 | object AlarmConstants {
4 |
5 | import Components._
6 | import AlertType._
7 | val SERVICE: String = "Spark"
8 |
9 | val STREAMING_APPLICATION_ERROR: String =
10 | "服务[" + SERVICE + "]-组件["+ STREAMING + "]-级别[" + Application + "] 异常告警"
11 |
12 | val STREAMING_BATCH_ERROR: String =
13 | "服务[" + SERVICE + "]-组件["+ STREAMING + "]-级别[" + Batch + "] 异常告警"
14 |
15 | val STREAMING_JOB_ERROR: String =
16 | "服务[" + SERVICE + "]-组件["+ STREAMING + "]-级别[" + Job + "] 异常告警"
17 | }
18 |
19 |
20 |
--------------------------------------------------------------------------------
/alarm-common/src/main/scala/com/netease/spark/alarm/Alarmist.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm
2 |
3 | trait Alarmist {
4 |
5 | /**
6 | * Send the alert message to possible external SMS, EMAIL, Phone system.
7 | *
8 | * @param msg the alert message to send
9 | * @return a [[AlertResp]] with status and an optional message
10 | */
11 | def alarm(msg: AlertMessage): AlertResp
12 | }
13 |
--------------------------------------------------------------------------------
/alarm-common/src/main/scala/com/netease/spark/alarm/AlertMessage.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm
2 |
3 | /**
4 | * Message used to be sent by [[Alarmist]]
5 | * @param title message title
6 | * @param content message body
7 | */
8 | case class AlertMessage(title: String, content: String)
9 |
--------------------------------------------------------------------------------
/alarm-common/src/main/scala/com/netease/spark/alarm/AlertResp.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm
2 |
3 | case class AlertResp(status: Boolean, ret: String)
4 |
5 | object AlertResp {
6 | def success(ret: String) = apply(status = true, ret)
7 | def failure(ret: String) = apply(status = false, ret)
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/alarm-common/src/main/scala/com/netease/spark/alarm/AlertType.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm
2 |
3 | /**
4 | *
5 | */
6 | object AlertType extends Enumeration {
7 | type AlertType = Value
8 | val Application, Batch, Job, Stage, Task = Value
9 | }
10 |
--------------------------------------------------------------------------------
/alarm-common/src/main/scala/com/netease/spark/alarm/Components.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm
2 |
3 | object Components extends Enumeration {
4 | type Components = Value
5 | val CORE, SQL, STREAMING, MLLIB, GRAPHX = Value
6 | }
7 |
--------------------------------------------------------------------------------
/alarm-email/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | spark-alarm
7 | com.netease.spark
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | alarm-email
13 | Spark Alarm Email
14 | jar
15 |
16 |
17 |
18 | com.netease.spark
19 | alarm-common
20 | ${project.version}
21 |
22 |
23 |
24 | org.apache.commons
25 | commons-email
26 |
27 |
28 |
29 | org.apache.spark
30 | spark-core_${scala.bin.ver}
31 |
32 |
33 |
34 | commons-logging
35 | commons-logging
36 | 1.2
37 |
38 |
39 |
40 |
41 |
42 | org.scalatest
43 | scalatest_${scala.bin.ver}
44 |
45 |
46 |
47 |
48 | target/scala-${scala.bin.ver}/classes
49 | target/scala-${scala.bin.ver}/test-classes
50 |
51 |
52 | ${project.basedir}/src/main/resources
53 |
54 |
55 |
56 |
57 | org.apache.maven.plugins
58 | maven-compiler-plugin
59 |
60 |
61 |
62 | net.alchim31.maven
63 | scala-maven-plugin
64 |
65 |
66 |
67 |
68 | org.apache.maven.plugins
69 | maven-surefire-plugin
70 |
71 |
72 |
73 | org.scalatest
74 | scalatest-maven-plugin
75 | 1.0
76 |
77 | ${project.build.directory}/surefire-reports
78 | .
79 | TestSuite.txt
80 |
81 | ${project.build.testOutputDirectory}/kyuubi-server-${project.version}.jar
82 |
83 |
84 | ${project.build.testOutputDirectory}/spark-warehouse
85 |
86 |
87 |
88 |
89 | test
90 |
91 | test
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/alarm-email/src/main/scala/com/netease/spark/alarm/email/EmailAlarmist.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.email
2 |
3 | import com.netease.spark.alarm.{Alarmist, AlertMessage, AlertResp}
4 | import org.apache.commons.logging.LogFactory
5 | import org.apache.commons.mail.{DefaultAuthenticator, EmailConstants, SimpleEmail}
6 | import org.apache.spark.SparkConf
7 |
8 | class EmailAlarmist(conf: SparkConf) extends Alarmist {
9 |
10 | private val LOG = LogFactory.getFactory.getInstance(classOf[EmailAlarmist])
11 | private val hostName = conf.get(HOSTNAME, "")
12 | private val useSSL = conf.getBoolean(SSL_ON_CONNECT, defaultValue = false)
13 | private val port = conf.getOption(SMTP_PORT) match {
14 | case Some(v) => v.toInt
15 | case _ => if (useSSL) 465 else 25
16 | }
17 | private val user = conf.get(USERNAME, "")
18 | private val password = conf.get(PASSWORD, "")
19 | private val from = conf.get(FROM, "")
20 | private val toList = conf.get(TO, from).split(",")
21 |
22 | override def alarm(msg: AlertMessage): AlertResp = {
23 | try {
24 | val email = new SimpleEmail()
25 | email.setHostName(hostName)
26 | email.setSmtpPort(port)
27 | email.setCharset(EmailConstants.UTF_8)
28 | val authn = new DefaultAuthenticator(user, password)
29 | email.setAuthenticator(authn)
30 | email.setSSLOnConnect(useSSL)
31 | email.setFrom(from)
32 | email.setSubject(msg.title)
33 | email.setMsg(msg.content)
34 | email.addTo(toList: _*)
35 | val ret = email.send()
36 | AlertResp.success(ret)
37 | } catch {
38 | case e: Exception =>
39 | LOG.error(s"User $user failed to send email from $from to $toList", e)
40 | AlertResp.failure(e.getMessage)
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/alarm-email/src/main/scala/com/netease/spark/alarm/email/package.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm
2 |
3 | package object email {
4 |
5 | private val PREFIX = "spark.alarm.email."
6 | val HOSTNAME = PREFIX + "smtp.host"
7 | val SMTP_PORT = PREFIX + "smtp.port"
8 | val USERNAME = PREFIX + "username"
9 | val PASSWORD = PREFIX + "password"
10 | val SSL_ON_CONNECT = PREFIX + "ssl.on.connect"
11 | val FROM = PREFIX + "from"
12 | val TO = PREFIX + "to"
13 | }
14 |
--------------------------------------------------------------------------------
/alarm-email/src/test/scala/com/netease/spark/alarm/email/EmailAlarmistTest.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.email
2 |
3 | import com.netease.spark.alarm.AlertMessage
4 | import org.apache.spark.SparkConf
5 | import org.scalatest.FunSuite
6 |
7 | class EmailAlarmistTest extends FunSuite {
8 | private val conf = new SparkConf(true)
9 | private val message = AlertMessage("title", "content")
10 | ignore("email alarm") {
11 | conf.set(HOSTNAME, "smtp-mail.outlook.com")
12 | .set(FROM, "yaooqinn@hotmail.com")
13 | .set(SSL_ON_CONNECT, "true")
14 | .set(USERNAME, "yaooqinn")
15 | .set(PASSWORD, "*****")
16 | .set(TO, "yaooqinn@hotmail.com")
17 | val alarmist = new EmailAlarmist(conf)
18 | val response = alarmist.alarm(message)
19 | assert(response.status)
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/alarm-sentry/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | spark-alarm
7 | com.netease.spark
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | alarm-sentry
13 | Spark Alarm Sentry
14 | jar
15 |
16 |
17 |
18 | com.netease.spark
19 | alarm-common
20 | ${project.version}
21 |
22 |
23 |
24 | org.apache.spark
25 | spark-core_${scala.bin.ver}
26 |
27 |
28 |
29 | org.apache.httpcomponents
30 | httpclient
31 | 4.5.13
32 |
33 |
34 |
35 | com.google.code.gson
36 | gson
37 |
38 |
39 |
40 |
41 |
42 | org.scalatest
43 | scalatest_${scala.bin.ver}
44 |
45 |
46 |
47 |
48 | target/scala-${scala.bin.ver}/classes
49 | target/scala-${scala.bin.ver}/test-classes
50 |
51 |
52 | ${project.basedir}/src/main/resources
53 |
54 |
55 |
56 |
57 | org.apache.maven.plugins
58 | maven-compiler-plugin
59 |
60 |
61 |
62 | net.alchim31.maven
63 | scala-maven-plugin
64 |
65 |
66 |
67 |
68 | org.apache.maven.plugins
69 | maven-surefire-plugin
70 |
71 |
72 |
73 | org.scalatest
74 | scalatest-maven-plugin
75 | 1.0
76 |
77 | ${project.build.directory}/surefire-reports
78 | .
79 | TestSuite.txt
80 |
81 | ${project.build.testOutputDirectory}/kyuubi-server-${project.version}.jar
82 |
83 |
84 | ${project.build.testOutputDirectory}/spark-warehouse
85 |
86 |
87 |
88 |
89 | test
90 |
91 | test
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/alarm-sentry/src/main/scala/com/netease/spark/alarm/sentry/SentryAlarmist.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.sentry
2 |
3 | import java.util
4 |
5 | import com.google.gson.Gson
6 | import com.netease.spark.alarm.{Alarmist, AlertMessage, AlertResp}
7 | import org.apache.commons.codec.digest.DigestUtils
8 | import org.apache.commons.codec.Charsets
9 | import org.apache.commons.logging.LogFactory
10 | import org.apache.http.client.entity.UrlEncodedFormEntity
11 | import org.apache.http.client.methods.HttpPost
12 | import org.apache.http.impl.client.HttpClients
13 | import org.apache.http.message.BasicNameValuePair
14 | import org.apache.http.util.EntityUtils
15 | import org.apache.spark.SparkConf
16 |
17 | import scala.collection.JavaConverters._
18 | import scala.util.control.NonFatal
19 |
20 | class SentryAlarmist(conf: SparkConf) extends Alarmist {
21 | private val LOG = LogFactory.getFactory.getInstance(classOf[SentryAlarmist])
22 |
23 | private val client = HttpClients.createDefault()
24 | private val url = conf.get(SENTRY_URL, "")
25 | private val typ = conf.get(SENTRY_API_TYPE, SentryType.Stone.toString)
26 | private val api = url + typ
27 | private val to = conf.get(SENTRY_TO_LIST, "")
28 | private val secret = conf.get(SENTRY_APP_SECRET, "")
29 | private val appName = conf.get(SENTRY_APP_NAME, "")
30 |
31 | override def alarm(msg: AlertMessage): AlertResp = {
32 |
33 | val httpPost = new HttpPost(api)
34 | val now = System.currentTimeMillis()
35 | val sig = DigestUtils.md5Hex(secret + now)
36 | val params = List(new BasicNameValuePair("to", to),
37 | new BasicNameValuePair("title", msg.title),
38 | new BasicNameValuePair("content", msg.content),
39 | new BasicNameValuePair("isSync", "true"),
40 | new BasicNameValuePair("ac_appName", appName),
41 | new BasicNameValuePair("ac_timestamp", now.toString),
42 | new BasicNameValuePair("ac_signature", sig))
43 | httpPost.setEntity(new UrlEncodedFormEntity(params.asJava, Charsets.UTF_8))
44 |
45 | try {
46 | val response = client.execute(httpPost)
47 | val status = response.getStatusLine.getStatusCode
48 | val resp = EntityUtils.toString(response.getEntity)
49 | if (status == 200) {
50 | val map = new Gson().fromJson(resp, classOf[util.HashMap[String, Any]])
51 | SentryStatus.handleResponse(map)
52 | } else {
53 | LOG.error(s"Failed to send message to $to using API $api, status: $status" )
54 | AlertResp.failure(resp)
55 | }
56 | } catch {
57 | case NonFatal(e) =>
58 | LOG.error(s"Failed to send message to $to using API $api", e)
59 | val message = e.getMessage
60 | AlertResp.failure(message)
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/alarm-sentry/src/main/scala/com/netease/spark/alarm/sentry/SentryStatus.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.sentry
2 |
3 | import com.netease.spark.alarm.AlertResp
4 | import org.apache.commons.logging.LogFactory
5 |
6 | object SentryStatus extends Enumeration {
7 | private val LOG = LogFactory.getFactory.getInstance(this.getClass)
8 |
9 | type SentryStatus = Value
10 |
11 | val SUCCESS: SentryStatus = Value(200, "SUCCESS")
12 | val PARTIAL_SUCCESS: SentryStatus = Value(201, "PARTIAL_SUCCESS")
13 | val SYSTEM_ERROR: SentryStatus = Value(500, "SYSTEM_ERROR")
14 | val AUTHENTICATION_ERROR: SentryStatus = Value(501, "AUTHENTICATION_ERROR")
15 | val ILLEGAL_ARGUMENTS: SentryStatus = Value(502, "ILLEGAL_ARGUMENTS")
16 | val QUOTA_EXCEED: SentryStatus = Value(520, "QUOTA_EXCEED")
17 |
18 | def handleResponse(resp: java.util.HashMap[String, Any]): AlertResp = {
19 | val msg = resp.get("message") + ""
20 | resp.get("code").asInstanceOf[Double].toInt match {
21 | case 200 =>
22 | LOG.info("Succeed to send alert to Sentry")
23 | AlertResp.success(msg)
24 | case 201 =>
25 | LOG.warn("Succeed to send alert to Sentry, but some receiver may not receive the alert")
26 | AlertResp.success(msg)
27 | case 500 =>
28 | LOG.error(s"${SYSTEM_ERROR.toString}: failed to send message to Sentry due to $msg")
29 | AlertResp.failure(msg)
30 | case 501 =>
31 | LOG.error(s"${AUTHENTICATION_ERROR.toString}: failed to send message to Sentry due to $msg")
32 | AlertResp.failure(msg)
33 | case 502 =>
34 | LOG.error(s"${ILLEGAL_ARGUMENTS.toString}: failed to send message to Sentry due to $msg")
35 | AlertResp.failure(msg)
36 | case 520 =>
37 | LOG.error(s"${QUOTA_EXCEED.toString}: failed to send message to Sentry due to $msg")
38 | AlertResp.failure(msg)
39 | case _ =>
40 | LOG.error(s"UNKNOWN_ERROR: failed to send message to Sentry due to $msg")
41 | AlertResp.failure(msg)
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/alarm-sentry/src/main/scala/com/netease/spark/alarm/sentry/SentryType.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.sentry
2 |
3 | object SentryType extends Enumeration {
4 |
5 | type SentryType = Value
6 |
7 | val /** 泡泡 */ POPO, /** 易信 */YiXin, /** 短信 */ SMS, /** 语音 */ Voice, Stone, /** 邮件 */ Email = Value
8 | }
9 |
--------------------------------------------------------------------------------
/alarm-sentry/src/main/scala/com/netease/spark/alarm/sentry/package.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm
2 |
3 | package object sentry {
4 | private val PREFIX = "spark.alarm.sentry."
5 | val SENTRY_URL: String = PREFIX + "url"
6 | val SENTRY_API_TYPE: String = PREFIX + "api.type"
7 | val SENTRY_TO_LIST: String = PREFIX + "to"
8 | val SENTRY_APP_SECRET: String = PREFIX + "app.secret"
9 | val SENTRY_APP_NAME: String = PREFIX + "app.name"
10 | }
11 |
--------------------------------------------------------------------------------
/alarm-sentry/src/test/scala/com/netease/spark/alarm/sentry/SentryAlarmistTest.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.sentry
2 |
3 | import com.netease.spark.alarm.AlertMessage
4 | import org.apache.spark.SparkConf
5 | import org.scalatest.FunSuite
6 |
7 | class SentryAlarmistTest extends FunSuite {
8 |
9 | private val conf = new SparkConf(true)
10 | private val message = AlertMessage("title", "content")
11 |
12 | ignore("sentry alarm") {
13 | conf.set(SENTRY_API_TYPE, "Stone")
14 | .set(SENTRY_APP_SECRET, "da1ce204-52aa-4d6a-835b-ea659dd2d0d3")
15 | .set(SENTRY_TO_LIST, "yaooqinn@hotmail.com")
16 | .set(SENTRY_APP_NAME, "NeSparkLogAnalyzer")
17 | val alarmist = new SentryAlarmist(conf)
18 | val response = alarmist.alarm(message)
19 | assert(!response.status)
20 | }
21 |
22 | ignore("smilodon alarm") {
23 | conf.set(SENTRY_API_TYPE, "Stone")
24 | .set(SENTRY_APP_SECRET, "4204243f-30bb-4bb9-9bdc-50f034778671")
25 | .set(SENTRY_TO_LIST, "hzyaoqin")
26 | .set(SENTRY_APP_NAME, "smilodon")
27 | val alarmist = new SentryAlarmist(conf)
28 | val response = alarmist.alarm(message)
29 | assert(response.status)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/alarm-smilodon/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | spark-alarm
7 | com.netease.spark
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | alarm-smilodon
13 | Spark Alarm Smilodon
14 | jar
15 |
16 |
17 |
18 | com.netease.spark
19 | alarm-common
20 | ${project.version}
21 |
22 |
23 |
24 | org.apache.spark
25 | spark-core_${scala.bin.ver}
26 |
27 |
28 |
29 | org.apache.httpcomponents
30 | httpclient
31 |
32 |
33 |
34 | com.google.code.gson
35 | gson
36 |
37 |
38 |
39 |
40 |
41 | org.scalatest
42 | scalatest_${scala.bin.ver}
43 |
44 |
45 |
46 |
47 | target/scala-${scala.bin.ver}/classes
48 | target/scala-${scala.bin.ver}/test-classes
49 |
50 |
51 | ${project.basedir}/src/main/resources
52 |
53 |
54 |
55 |
56 | org.apache.maven.plugins
57 | maven-compiler-plugin
58 |
59 |
60 |
61 | net.alchim31.maven
62 | scala-maven-plugin
63 |
64 |
65 |
66 |
67 | org.apache.maven.plugins
68 | maven-surefire-plugin
69 |
70 |
71 |
72 | org.scalatest
73 | scalatest-maven-plugin
74 | 1.0
75 |
76 | ${project.build.directory}/surefire-reports
77 | .
78 | TestSuite.txt
79 |
80 | ${project.build.testOutputDirectory}/kyuubi-server-${project.version}.jar
81 |
82 |
83 | ${project.build.testOutputDirectory}/spark-warehouse
84 |
85 |
86 |
87 |
88 | test
89 |
90 | test
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/alarm-smilodon/src/main/scala/com/netease/spark/alarm/smilodon/SmilodonAlarmist.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.smilodon
2 |
3 | import com.google.gson.Gson
4 | import com.netease.spark.alarm.{Alarmist, AlertMessage, AlertResp}
5 | import org.apache.commons.logging.LogFactory
6 | import org.apache.http.client.methods.HttpPost
7 | import org.apache.http.entity.{ContentType, StringEntity}
8 | import org.apache.http.impl.client.HttpClients
9 | import org.apache.http.util.EntityUtils
10 | import org.apache.spark.SparkConf
11 |
12 | import scala.util.control.NonFatal
13 |
14 | class SmilodonAlarmist(conf: SparkConf) extends Alarmist {
15 | private val LOG = LogFactory.getFactory.getInstance(classOf[SmilodonAlarmist])
16 |
17 | private val client = HttpClients.createDefault()
18 | private val url = conf.get(SMILODON_URL, "http://10.165.139.76:8765/v1/alerts")
19 | override def alarm(msg: AlertMessage): AlertResp = {
20 | val httpPost = new HttpPost(url)
21 | val entity = new StringEntity(msg.content, ContentType.APPLICATION_JSON)
22 | // httpPost.setHeader("Content-Type", "application/json")
23 | httpPost.setEntity(entity)
24 | try {
25 | val response = client.execute(httpPost)
26 | val code = response.getStatusLine.getStatusCode
27 | val resp = EntityUtils.toString(response.getEntity)
28 |
29 | if (code == 200) {
30 | LOG.info("Succeed to send alert to Smilondon")
31 | AlertResp.success(resp)
32 | } else {
33 | LOG.error(s"Failed to send alert to Smilondon, status: $code, msg: $resp")
34 | AlertResp.failure(resp)
35 | }
36 | } catch {
37 | case NonFatal(e) =>
38 | LOG.error("Failed to send alert to Smilondon " + msg.content , e)
39 | AlertResp.failure(e.getMessage)
40 | }
41 | }
42 |
43 | def alarm(msg: SmilodonAlertMessage): AlertResp = {
44 | val jsonMsg = new Gson().toJson(msg)
45 | alarm(AlertMessage("", jsonMsg))
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/alarm-smilodon/src/main/scala/com/netease/spark/alarm/smilodon/SmilodonAlertLevel.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.smilodon
2 |
3 | object SmilodonAlertLevel extends Enumeration {
4 |
5 | type SmilodonAlertLevel = Value
6 |
7 | val GENERAL: SmilodonAlertLevel = Value(1, "general")
8 | val NORMAL: SmilodonAlertLevel = Value(2, "normal")
9 | val WARN: SmilodonAlertLevel = Value(3, "warn")
10 | val ERROR: SmilodonAlertLevel = Value(4, "error")
11 | val FATAL: SmilodonAlertLevel = Value(5, "fatal")
12 |
13 | def getIdByLevel(level: SmilodonAlertLevel): Int = {
14 | level match {
15 | case NORMAL => 2
16 | case WARN => 3
17 | case ERROR => 4
18 | case FATAL => 5
19 | case _ => 1
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/alarm-smilodon/src/main/scala/com/netease/spark/alarm/smilodon/SmilodonAlertMessage.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.smilodon
2 |
3 | import java.util
4 |
5 | import com.netease.spark.alarm.smilodon.SmilodonAlertLevel.SmilodonAlertLevel
6 | import com.netease.spark.alarm.smilodon.channel.{ChannelType, SmilodonChannel}
7 | import com.netease.spark.alarm.smilodon.event.SmilodonEvent
8 | import org.apache.spark.SparkConf
9 |
10 | import scala.collection.JavaConverters._
11 |
12 | /**
13 | *
14 | * @param level_id 不填时默认值为1
15 | * @param event
16 | * @param default_title 默认报警信息标题,如发送邮件的主题;不为空时,若channel无title字段,将使用本字段值,若多channel的title相同,建议设置此字段;
17 | * 可为空,使用系统默认title
18 | * @param default_content 默认报警信息;不为空时,若channel无content字段,将使用本字段值,若多channel的content相同,建议设置此字段;可为空,使用
19 | * 系统默认content
20 | * @param channels 报警通道列表,可定制该通道的报警标题和信息
21 | * @param users 接收人email列表
22 | * @param groups 报警组id 列表
23 | */
24 | case class SmilodonAlertMessage private(
25 | level_id: Int,
26 | event: SmilodonEvent,
27 | default_title: String,
28 | default_content: String,
29 | channels: java.util.List[SmilodonChannel],
30 | users: java.util.List[String],
31 | groups: java.util.List[String])
32 |
33 | object SmilodonAlertMessage {
34 | def apply(
35 | level: SmilodonAlertLevel,
36 | event: SmilodonEvent,
37 | default_title: String,
38 | default_content: String,
39 | channels: util.List[SmilodonChannel],
40 | users: util.List[String],
41 | groups: util.List[String]): SmilodonAlertMessage = {
42 | val id = SmilodonAlertLevel.getIdByLevel(level)
43 | new SmilodonAlertMessage(id, event, default_title, default_content, channels, users, groups)
44 | }
45 |
46 | def apply(
47 | level: SmilodonAlertLevel,
48 | event: SmilodonEvent,
49 | default_title: String,
50 | default_content: String,
51 | channels: util.List[SmilodonChannel],
52 | users: util.List[String]): SmilodonAlertMessage = {
53 | val id = SmilodonAlertLevel.getIdByLevel(level)
54 | new SmilodonAlertMessage(id, event, default_title, default_content, channels, users, Seq.empty[String].asJava)
55 | }
56 |
57 | def apply(
58 | event: SmilodonEvent,
59 | default_title: String,
60 | default_content: String,
61 | channels: util.List[SmilodonChannel],
62 | users: util.List[String]): SmilodonAlertMessage = {
63 | new SmilodonAlertMessage(1, event, default_title, default_content, channels, users, Seq.empty[String].asJava)
64 | }
65 |
66 | def apply(
67 | level: SmilodonAlertLevel,
68 | event: SmilodonEvent,
69 | default_title: String,
70 | default_content: String,
71 | conf: SparkConf): SmilodonAlertMessage = {
72 | val id = SmilodonAlertLevel.getIdByLevel(level)
73 | val channels = conf.get(SMILODON_ALERT_CHANNELS, "POPO,STONE")
74 | .split(",").map(_.trim)
75 | .map(ChannelType.getIdByName)
76 | .map(SmilodonChannel(_, "", "")).toList.asJava
77 | val users = conf.get(SMILODON_ALERT_USERS, "").split(",").map(_.trim).toList.asJava
78 | val groups = conf.getOption(SMILODON_ALERT_GROUPS) match {
79 | case Some(li) if li.trim.nonEmpty => li.split(",").map(_.trim).toList.asJava
80 | case _ => Seq.empty[String].asJava
81 | }
82 | new SmilodonAlertMessage(id, event, default_title, default_content, channels, users, groups)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/alarm-smilodon/src/main/scala/com/netease/spark/alarm/smilodon/channel/ChannelType.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.smilodon.channel
2 |
3 | object ChannelType extends Enumeration {
4 |
5 | type ChannelType = Value
6 |
7 | val UNSUPPORTED = Value(1, "EMAIL, SMS, VOICE, YIXIN, etc.")
8 | val POPO = Value(4, "POPO")
9 | val STONE = Value(5, "Stone")
10 |
11 | def getIdByType(typ: ChannelType): Int = typ match {
12 | case POPO => 4
13 | case STONE => 5
14 | case _ => 1
15 | }
16 |
17 | def getIdByName(name: String): Int = name.toUpperCase() match {
18 | case "POPO" => 4
19 | case "STONE" => 5
20 | case _ => 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/alarm-smilodon/src/main/scala/com/netease/spark/alarm/smilodon/channel/SmilodonChannel.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.smilodon.channel
2 |
3 | import com.netease.spark.alarm.smilodon.channel.ChannelType.ChannelType
4 |
5 | case class SmilodonChannel private (channel_id: Int, title: String, content: String)
6 |
7 | object SmilodonChannel {
8 | def apply(typ: ChannelType, title: String, content: String): SmilodonChannel = {
9 | val id = ChannelType.getIdByType(typ)
10 | new SmilodonChannel(id, title, content)
11 | }
12 |
13 | /**
14 | * Send alert message to Smilodon, then it will be alarm by POPO
15 | */
16 | def popo(title: String, content: String): SmilodonChannel = {
17 | SmilodonChannel(4, title, content)
18 | }
19 |
20 | /**
21 | * Send alert message to Smilodon, then it will be alarm by POPO
22 | */
23 | def popo(): SmilodonChannel = {
24 | SmilodonChannel(4, "", "")
25 | }
26 |
27 | /**
28 | * Send alert message to Smilodon, then it will be alarm by Stone
29 | */
30 | def stone(title: String, content: String): SmilodonChannel = {
31 | SmilodonChannel(5, title, content)
32 | }
33 |
34 | /**
35 | * Send alert message to Smilodon, then it will be alarm by Stone
36 | */
37 | def stone(): SmilodonChannel = {
38 | SmilodonChannel(5, "", "")
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/alarm-smilodon/src/main/scala/com/netease/spark/alarm/smilodon/event/SmilodonEvent.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.smilodon.event
2 |
3 | import com.netease.spark.alarm.{AlarmConstants, AlertType, Components}
4 |
5 | /**
6 | *
7 | * @param service 报警是由哪个服务产生的
8 | * @param component 报警是由服务的哪个组件产生的
9 | * @param `type` 具体的事件类型,比如任务失败,服务挂掉等
10 | */
11 | case class SmilodonEvent(service: String, component: String, `type`: String)
12 |
13 | object SmilodonEvent {
14 | def streamingAppEvent(): SmilodonEvent =
15 | SmilodonEvent(AlarmConstants.SERVICE, Components.STREAMING.toString, AlertType.Application.toString)
16 |
17 | def streamingBatchEvent(): SmilodonEvent =
18 | SmilodonEvent(AlarmConstants.SERVICE, Components.STREAMING.toString, AlertType.Batch.toString)
19 |
20 | def streamingJobEvent(): SmilodonEvent =
21 | SmilodonEvent(AlarmConstants.SERVICE, Components.STREAMING.toString, AlertType.Job.toString)
22 | }
23 |
--------------------------------------------------------------------------------
/alarm-smilodon/src/main/scala/com/netease/spark/alarm/smilodon/package.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm
2 |
3 | package object smilodon {
4 |
5 | private val PREFIX = "spark.alarm.smilodon."
6 | val SMILODON_URL: String = PREFIX + "url"
7 | val SMILODON_ALERT_CHANNELS: String = PREFIX + "channels"
8 | val SMILODON_ALERT_USERS: String = PREFIX + "users"
9 | val SMILODON_ALERT_GROUPS: String = PREFIX + "groups"
10 | }
11 |
--------------------------------------------------------------------------------
/alarm-smilodon/src/test/scala/com/netease/spark/alarm/smilodon/SmilodonAlarmistTest.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.smilodon
2 |
3 | import com.netease.spark.alarm.smilodon.channel.{ChannelType, SmilodonChannel}
4 | import com.netease.spark.alarm.smilodon.event.SmilodonEvent
5 | import org.apache.spark.SparkConf
6 | import org.scalatest.FunSuite
7 |
8 | import scala.collection.JavaConverters._
9 |
10 | class SmilodonAlarmistTest extends FunSuite {
11 | private val conf = new SparkConf(true)
12 | private val event = SmilodonEvent("TEST", "ALERT", "TASK_FAILED")
13 |
14 | ignore("smilodon alarm") {
15 | val stone = SmilodonChannel.stone()
16 | val popo = SmilodonChannel(ChannelType.POPO, "title", "spark 服务有异常吗?")
17 | val msg = SmilodonAlertMessage(
18 | SmilodonAlertLevel.WARN,
19 | event,
20 | "Spark 服务异常",
21 | "Spark 服务没什么异常",
22 | Seq(stone, popo).asJava,
23 | List("yaooqinn@hotmail.com").asJava)
24 |
25 | val alarmist = new SmilodonAlarmist(conf)
26 | val response = alarmist.alarm(msg)
27 | assert(response.status)
28 | }
29 |
30 | ignore("smilodon alarm using spark conf") {
31 | conf.set("spark.alarm.smilodon.users", "yaooqinn@hotmail.com")
32 | .set("spark.alarm.smilodon.channels", "POPO,STONE")
33 |
34 | val msg = SmilodonAlertMessage(
35 | SmilodonAlertLevel.WARN,
36 | event,
37 | "Spark 服务异常",
38 | "Spark 服务没什么异常",
39 | conf)
40 |
41 | val alarmist = new SmilodonAlarmist(conf)
42 | val response = alarmist.alarm(msg)
43 | assert(response.status)
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.netease.spark
8 | spark-alarm
9 | Spark Alarm Parent
10 | pom
11 | 1.0-SNAPSHOT
12 |
13 | alarm-common
14 | alarm-email
15 | alarm-sentry
16 | alarm-smilodon
17 | streaming-alarmer
18 |
19 |
20 |
21 | 1.5
22 | 1.2.49
23 | 2.11
24 | 2.11.8
25 | 2.4.7
26 |
27 |
28 |
29 |
30 |
31 | org.scala-lang
32 | scala-library
33 | ${scala.ver}
34 | provided
35 |
36 |
37 |
38 | org.apache.commons
39 | commons-email
40 | ${common.email.ver}
41 | compile
42 |
43 |
44 |
45 | org.apache.spark
46 | spark-core_${scala.bin.ver}
47 | ${spark.ver}
48 | provided
49 |
50 |
51 |
52 | org.apache.spark
53 | spark-streaming_${scala.bin.ver}
54 | ${spark.ver}
55 | provided
56 |
57 |
58 |
59 | org.apache.httpcomponents
60 | httpclient
61 | 4.5.6
62 | provided
63 |
64 |
65 |
66 | com.google.code.gson
67 | gson
68 | 2.2.4
69 | provided
70 |
71 |
72 |
73 | commons-logging
74 | commons-logging
75 | 1.1.3
76 | provided
77 |
78 |
79 |
80 |
81 | org.scalatest
82 | scalatest_${scala.bin.ver}
83 | 2.2.6
84 | test
85 |
86 |
87 |
88 |
89 |
90 |
91 | target/scala-${scala.bin.ver}/classes
92 | target/scala-${scala.bin.ver}/test-classes
93 |
94 |
95 |
96 | org.apache.maven.plugins
97 | maven-compiler-plugin
98 | 3.5.1
99 |
100 | ${java.version}
101 | ${java.version}
102 | UTF-8
103 | 1024m
104 | true
105 |
106 | -Xlint:all,-serial,-path
107 |
108 |
109 |
110 |
111 |
112 | net.alchim31.maven
113 | scala-maven-plugin
114 | 3.3.1
115 |
116 |
117 | eclipse-add-source
118 |
119 | add-source
120 |
121 |
122 |
123 | scala-compile-first
124 |
125 | compile
126 |
127 |
128 |
129 | scala-test-compile-first
130 |
131 | testCompile
132 |
133 |
134 |
135 |
136 | ${scala.ver}
137 | incremental
138 | true
139 |
140 | -unchecked
141 | -deprecation
142 | -feature
143 | -explaintypes
144 | -Yno-adapted-args
145 |
146 |
147 | -Xms1024m
148 | -Xmx1024m
149 | -XX:ReservedCodeCacheSize=512M
150 |
151 |
152 | -source
153 | ${java.version}
154 | -target
155 | ${java.version}
156 | -Xlint:all,-serial,-path,-try
157 |
158 |
159 |
160 |
161 |
162 |
163 | org.apache.maven.plugins
164 | maven-surefire-plugin
165 | 2.12.4
166 |
167 | true
168 |
169 |
170 |
171 |
172 | org.scalatest
173 | scalatest-maven-plugin
174 | 1.0
175 |
176 | ${project.build.directory}/surefire-reports
177 | .
178 | TestSuite.txt
179 |
180 |
181 |
182 | test
183 |
184 | test
185 |
186 |
187 |
188 |
189 |
190 |
191 | org.jacoco
192 | jacoco-maven-plugin
193 | 0.8.0
194 |
195 |
196 |
197 | org.apache.maven.plugins
198 | maven-shade-plugin
199 | 3.2.1
200 |
201 |
202 |
203 | org.apache.maven.plugins
204 | maven-jar-plugin
205 | 3.0.2
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 | spark-2.1
214 |
215 | 2.1.3
216 |
217 |
218 |
219 |
220 | spark-2.2
221 |
222 | 2.2.2
223 |
224 |
225 |
226 |
227 | spark-2.3
228 |
229 | 2.3.2
230 | 3.0.3
231 |
232 |
233 |
234 |
235 | spark-2.4
236 |
237 | 2.4.0
238 | 3.0.3
239 |
240 |
241 |
242 |
243 |
--------------------------------------------------------------------------------
/streaming-alarmer/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | spark-alarm
7 | com.netease.spark
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | streaming-alarmer
13 | Spark Streaming Alarmer
14 | jar
15 |
16 |
17 |
18 | org.scala-lang
19 | scala-library
20 |
21 |
22 |
23 | com.netease.spark
24 | alarm-common
25 | ${project.version}
26 |
27 |
28 |
29 | com.netease.spark
30 | alarm-email
31 | ${project.version}
32 |
33 |
34 |
35 | com.netease.spark
36 | alarm-sentry
37 | ${project.version}
38 |
39 |
40 |
41 | com.netease.spark
42 | alarm-smilodon
43 | ${project.version}
44 |
45 |
46 |
47 | org.apache.spark
48 | spark-core_${scala.bin.ver}
49 |
50 |
51 |
52 | org.apache.spark
53 | spark-streaming_${scala.bin.ver}
54 | provided
55 |
56 |
57 |
58 | org.apache.httpcomponents
59 | httpclient
60 |
61 |
62 |
63 | com.google.code.gson
64 | gson
65 |
66 |
67 |
68 |
69 | org.scalatest
70 | scalatest_${scala.bin.ver}
71 |
72 |
73 |
74 |
75 | target/scala-${scala.bin.ver}/classes
76 | target/scala-${scala.bin.ver}/test-classes
77 |
78 |
79 | ${project.basedir}/src/main/resources
80 |
81 |
82 |
83 |
84 | org.apache.maven.plugins
85 | maven-compiler-plugin
86 |
87 |
88 |
89 | net.alchim31.maven
90 | scala-maven-plugin
91 |
92 |
93 |
94 | maven-shade-plugin
95 |
96 | false
97 |
98 |
99 | com.netease.spark:alarm-common
100 | com.netease.spark:alarm-email
101 | com.netease.spark:alarm-sentry
102 | com.netease.spark:alarm-smilodon
103 | org.apache.commons:commons-email
104 | com.sun.mail:javax.mail
105 |
106 |
107 |
108 |
109 |
110 | package
111 |
112 | shade
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | org.apache.maven.plugins
122 | maven-surefire-plugin
123 |
124 |
125 |
126 | org.scalatest
127 | scalatest-maven-plugin
128 | 1.0
129 |
130 | ${project.build.directory}/surefire-reports
131 | .
132 | TestSuite.txt
133 |
134 | ${project.build.testOutputDirectory}/kyuubi-server-${project.version}.jar
135 |
136 |
137 | ${project.build.testOutputDirectory}/spark-warehouse
138 |
139 |
140 |
141 |
142 | test
143 |
144 | test
145 |
146 |
147 |
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/streaming-alarmer/src/main/scala/com/netease/spark/alarm/alarmer/streaming/EmailStreamingAlarmListener.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.alarmer.streaming
2 | import com.netease.spark.alarm.Alarmist
3 | import com.netease.spark.alarm.email.EmailAlarmist
4 | import org.apache.spark.SparkConf
5 |
6 | class EmailStreamingAlarmListener(override val conf: SparkConf) extends StreamingAlarmListener {
7 | override val alarmist: Alarmist = new EmailAlarmist(conf)
8 | }
9 |
--------------------------------------------------------------------------------
/streaming-alarmer/src/main/scala/com/netease/spark/alarm/alarmer/streaming/SentryStreamingAlarmListener.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.alarmer.streaming
2 | import com.netease.spark.alarm.Alarmist
3 | import com.netease.spark.alarm.sentry.SentryAlarmist
4 | import org.apache.spark.SparkConf
5 |
6 | class SentryStreamingAlarmListener(override val conf: SparkConf) extends StreamingAlarmListener {
7 | override val alarmist: Alarmist = new SentryAlarmist(conf)
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/streaming-alarmer/src/main/scala/com/netease/spark/alarm/alarmer/streaming/SmilodonStreamingAlarmListener.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.alarmer.streaming
2 | import java.text.SimpleDateFormat
3 | import java.util.Date
4 |
5 | import com.netease.spark.alarm.smilodon.{SmilodonAlarmist, SmilodonAlertLevel, SmilodonAlertMessage}
6 | import com.netease.spark.alarm.smilodon.event.SmilodonEvent
7 | import com.netease.spark.alarm.AlarmConstants._
8 | import org.apache.commons.logging.LogFactory
9 | import org.apache.spark.SparkConf
10 | import org.apache.spark.scheduler.SparkListenerApplicationEnd
11 | import org.apache.spark.streaming.StreamingContext
12 | import org.apache.spark.streaming.scheduler.{StreamingListenerBatchCompleted, StreamingListenerBatchStarted, StreamingListenerOutputOperationCompleted}
13 |
14 | import scala.collection.JavaConverters._
15 |
16 | class SmilodonStreamingAlarmListener(override val conf: SparkConf) extends StreamingAlarmListener {
17 | private val LOG = LogFactory.getFactory.getInstance(classOf[SmilodonStreamingAlarmListener])
18 | override val alarmist: SmilodonAlarmist = new SmilodonAlarmist(conf)
19 |
20 | override def onApplicationEnd(applicationEnd: SparkListenerApplicationEnd): Unit = {
21 | if (LOG.isDebugEnabled) LOG.debug(s"onApplicationEnd called: $applicationEnd")
22 |
23 | val maybeContext = StreamingContext.getActive()
24 | maybeContext.foreach { ssc =>
25 | val context = ssc.sparkContext
26 | val dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
27 | val startTime = dateFormat.format(new Date(context.startTime))
28 | val endTime = dateFormat.format(new Date(applicationEnd.time))
29 | val content = appFailContent(startTime, endTime)
30 | LOG.error(content)
31 | val event = SmilodonEvent.streamingAppEvent()
32 | val alertMessage = SmilodonAlertMessage(SmilodonAlertLevel.FATAL, event, STREAMING_APPLICATION_ERROR, content, conf)
33 | alarmist.alarm(alertMessage)
34 | // Spark 内部机制会保障Streaming 无法在ListenerBus中被调用
35 | // ssc.stop(stopSparkContext = false, stopGracefully = false)
36 | }
37 |
38 | if (conf.getBoolean(FORCE_EXIT, defaultValue = false)) {
39 | System.exit(-1)
40 | }
41 | }
42 |
43 | override def onBatchStarted(batchStarted: StreamingListenerBatchStarted): Unit = {
44 | if (LOG.isDebugEnabled) LOG.debug(s"onBatchStarted called: $batchStarted")
45 |
46 | if (batchNoticeEnable) {
47 | val currentTime = System.currentTimeMillis()
48 | batchSet.entrySet().asScala.foreach { kv =>
49 | val start = kv.getValue
50 | if (currentTime - start > batchProcessThreshold) {
51 | val content = batchTimeoutContent(currentTime - start)
52 | val event = SmilodonEvent.streamingBatchEvent()
53 | val alertMessage = SmilodonAlertMessage(SmilodonAlertLevel.FATAL, event, STREAMING_BATCH_ERROR, content, conf)
54 | alarmist.alarm(alertMessage)
55 | }
56 | }
57 | val batchInfo = batchStarted.batchInfo
58 | batchSet.put(batchInfo.batchTime, batchInfo.processingStartTime.getOrElse(System.currentTimeMillis()))
59 | }
60 | }
61 |
62 | override def onBatchCompleted(batchCompleted: StreamingListenerBatchCompleted): Unit = {
63 | if (LOG.isDebugEnabled) LOG.debug(s"onBatchCompleted called: $batchCompleted")
64 |
65 | val info = batchCompleted.batchInfo
66 |
67 | if (batchNoticeEnable) {
68 | batchSet.remove(info.batchTime)
69 | val batchFailureReasons = info.outputOperationInfos.values.map(_.failureReason).filter(_.nonEmpty).mkString("\n")
70 | if (batchFailureReasons.nonEmpty) {
71 | val content = batchErrorContent(batchFailureReasons)
72 | val event = SmilodonEvent.streamingBatchEvent()
73 | val alertMessage = SmilodonAlertMessage(SmilodonAlertLevel.FATAL, event, STREAMING_BATCH_ERROR, content, conf)
74 | alarmist.alarm(alertMessage)
75 | }
76 | }
77 |
78 | val schedulingDelay = info.schedulingDelay.getOrElse(0L)
79 | val processingDelay = info.processingDelay.getOrElse(Long.MaxValue)
80 | if (processingDelay > batchProcessThreshold && schedulingDelay / processingDelay > batchDelayRatio) {
81 | val content = batchDelayContent(schedulingDelay, processingDelay)
82 | val event = SmilodonEvent.streamingBatchEvent()
83 | val alertMessage = SmilodonAlertMessage(SmilodonAlertLevel.FATAL, event, STREAMING_BATCH_ERROR, content, conf)
84 | alarmist.alarm(alertMessage)
85 | }
86 | }
87 |
88 | override def onOutputOperationCompleted(outputOperationCompleted: StreamingListenerOutputOperationCompleted): Unit = {
89 | if (LOG.isDebugEnabled) LOG.debug(s"onOutputOperationCompleted called: $outputOperationCompleted")
90 |
91 | if (jobNoticeEnable) {
92 | val jobFailureReason = outputOperationCompleted.outputOperationInfo.failureReason
93 | jobFailureReason.foreach { reason =>
94 | val content = jobErrorContent(reason)
95 | val event = SmilodonEvent.streamingJobEvent()
96 | val alertMessage = SmilodonAlertMessage(SmilodonAlertLevel.FATAL, event, STREAMING_JOB_ERROR, content, conf)
97 | alarmist.alarm(alertMessage)
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/streaming-alarmer/src/main/scala/com/netease/spark/alarm/alarmer/streaming/StreamingAlarmListener.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.alarmer.streaming
2 |
3 | import java.text.SimpleDateFormat
4 | import java.util.Date
5 | import java.util.concurrent.ConcurrentHashMap
6 |
7 | import com.netease.spark.alarm.{Alarmist, AlertMessage}
8 | import com.netease.spark.alarm.AlarmConstants._
9 | import org.apache.commons.logging.LogFactory
10 | import org.apache.spark.scheduler.{SparkListener, SparkListenerApplicationEnd, SparkListenerApplicationStart, SparkListenerEvent}
11 | import org.apache.spark.streaming.scheduler.{StreamingListener, StreamingListenerBatchCompleted, StreamingListenerBatchStarted, StreamingListenerOutputOperationCompleted}
12 | import org.apache.spark.SparkConf
13 | import org.apache.spark.streaming.{StreamingContext, Time}
14 |
15 | import scala.util.{Success, Try}
16 | import scala.collection.JavaConverters._
17 |
18 | trait StreamingAlarmListener extends SparkListener with StreamingListener {
19 |
20 | import StreamingAlarmListener._
21 |
22 | private val LOG = LogFactory.getFactory.getInstance(classOf[StreamingAlarmListener])
23 |
24 | protected def alarmist: Alarmist
25 |
26 | protected def conf: SparkConf
27 |
28 | protected var applicationId: String = conf.getAppId
29 | protected var appName: String = conf.get("spark.app.name")
30 |
31 | protected val batchNoticeEnable: Boolean = conf.getBoolean(BATCH_NOTICE_ENABLE, defaultValue = true)
32 | protected val batchDelayRatio: Double = conf.getDouble(BATCH_DELAY_RATIO, 1)
33 | protected val batchProcessThreshold: Long = conf.getTimeAsMs(BATCH_PROCESS_TIME, "1s")
34 | protected lazy val batchSet: java.util.Map[Time, Long] = new ConcurrentHashMap[Time, Long]
35 |
36 | protected val jobNoticeEnable: Boolean = conf.getBoolean(JOB_NOTICE_ENABLE, defaultValue = false)
37 |
38 | override def onApplicationStart(applicationStart: SparkListenerApplicationStart): Unit = {
39 | if (LOG.isDebugEnabled) LOG.debug(s"onApplicationStart called: $applicationStart")
40 |
41 | StreamingContext.getActive().foreach { ssc =>
42 | val context = ssc.sparkContext
43 | applicationId = context.applicationId
44 | appName = context.appName
45 | }
46 | }
47 | /**
48 | * 用于捕获SparkContext变量stop, 但流式作业未正常结束
49 | * 场景举例:
50 | * ApplicationMaster failover失败,当Driver端的Application Monitor检测到AM跪了,
51 | * 会触发调用SparkContext.stop
52 | */
53 | override def onApplicationEnd(applicationEnd: SparkListenerApplicationEnd): Unit = {
54 | if (LOG.isDebugEnabled) LOG.debug(s"onApplicationEnd called: $applicationEnd")
55 |
56 | val maybeContext = StreamingContext.getActive()
57 | maybeContext.foreach { ssc =>
58 | val context = ssc.sparkContext
59 | val dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
60 | val startTime = dateFormat.format(new Date(context.startTime))
61 | val endTime = dateFormat.format(new Date(applicationEnd.time))
62 | val content = appFailContent(startTime, endTime)
63 | alarmist.alarm(AlertMessage(STREAMING_APPLICATION_ERROR, content))
64 | // ssc.stop(stopSparkContext = false, stopGracefully = false)
65 | }
66 |
67 | if (conf.getBoolean(FORCE_EXIT, defaultValue = false)) {
68 | System.exit(-1)
69 | }
70 | }
71 |
72 | /**
73 | * In [[SparkListener]]s, wrapped streaming events first come here
74 | */
75 | override def onOtherEvent(event: SparkListenerEvent): Unit = {
76 | if (LOG.isDebugEnabled) LOG.debug(s"onOtherEvent called: $event")
77 |
78 | Try { event.getClass.getDeclaredField("streamingListenerEvent") } match {
79 | case Success(field) =>
80 | field.setAccessible(true)
81 | field.get(event) match {
82 | case as: SparkListenerApplicationStart => onApplicationStart(as)
83 | case ae: SparkListenerApplicationEnd => onApplicationEnd(ae)
84 | case bs: StreamingListenerBatchStarted => onBatchStarted(bs)
85 | case bc: StreamingListenerBatchCompleted => onBatchCompleted(bc)
86 | case oc: StreamingListenerOutputOperationCompleted => onOutputOperationCompleted(oc)
87 | case _ =>
88 | }
89 | case _ =>
90 | }
91 | }
92 |
93 | override def onBatchStarted(batchStarted: StreamingListenerBatchStarted): Unit = {
94 | if (LOG.isDebugEnabled) LOG.debug(s"onBatchStarted called: $batchStarted")
95 |
96 | if (batchNoticeEnable) {
97 | val currentTime = System.currentTimeMillis()
98 | batchSet.entrySet().asScala.foreach { kv =>
99 | val start = kv.getValue
100 | if (currentTime - start > batchProcessThreshold) {
101 | alarmist.alarm(AlertMessage(STREAMING_BATCH_ERROR,
102 | batchTimeoutContent(currentTime - start)))
103 | }
104 | }
105 | val batchInfo = batchStarted.batchInfo
106 | batchSet.put(batchInfo.batchTime, batchInfo.processingStartTime.getOrElse(System.currentTimeMillis()))
107 | }
108 | }
109 |
110 | override def onBatchCompleted(batchCompleted: StreamingListenerBatchCompleted): Unit = {
111 | if (LOG.isDebugEnabled) LOG.debug(s"onBatchCompleted called: $batchCompleted")
112 |
113 | val info = batchCompleted.batchInfo
114 |
115 | if (batchNoticeEnable) {
116 | batchSet.remove(info.batchTime)
117 | val batchFailureReasons = info.outputOperationInfos.values.map(_.failureReason).filter(_.nonEmpty).mkString("\n")
118 | if (batchFailureReasons.nonEmpty) {
119 | val content = batchErrorContent(batchFailureReasons)
120 | alarmist.alarm(AlertMessage(STREAMING_BATCH_ERROR, content))
121 | }
122 | }
123 |
124 | val schedulingDelay = info.schedulingDelay.getOrElse(0L)
125 | val processingDelay = info.processingDelay.getOrElse(Long.MaxValue)
126 | if (processingDelay > batchProcessThreshold && schedulingDelay / processingDelay > batchDelayRatio) {
127 | val content = batchDelayContent(schedulingDelay, processingDelay)
128 | alarmist.alarm(AlertMessage(STREAMING_BATCH_ERROR, content))
129 | }
130 | }
131 |
132 | override def onOutputOperationCompleted(outputOperationCompleted: StreamingListenerOutputOperationCompleted): Unit = {
133 | if (LOG.isDebugEnabled) LOG.debug(s"onOutputOperationCompleted called: $outputOperationCompleted")
134 |
135 | if (jobNoticeEnable) {
136 | val jobFailureReason = outputOperationCompleted.outputOperationInfo.failureReason
137 | jobFailureReason.foreach { reason =>
138 | alarmist.alarm(AlertMessage(STREAMING_JOB_ERROR, jobErrorContent(reason)))
139 | }
140 | }
141 | }
142 |
143 | protected def batchDelayContent(schedulingDelay: Long, processingDelay: Long): String = {
144 | s"""
145 | |$STREAMING_BATCH_ERROR
146 | |
147 | |作业名: $appName
148 | |作业ID: $applicationId
149 | |诊断信息:
150 | | 批次调度延时过高(Scheduling Delay: $schedulingDelay ms / Processing Time: $processingDelay ms > $batchDelayRatio )
151 | | 单独关闭该类型警报可设置 $BATCH_DELAY_RATIO 的值
152 | """.stripMargin
153 | }
154 |
155 | protected def batchErrorContent(batchFailureReasons: String): String = {
156 | s"""
157 | |$STREAMING_BATCH_ERROR
158 | |
159 | |作业名: $appName
160 | |作业ID: $applicationId
161 | |诊断信息:
162 | | (长度限制: $failureReasonLimit)
163 | | ${getLimitedFailureReason(batchFailureReasons)}
164 | | 单独关闭该类型警报可设置$BATCH_NOTICE_ENABLE=false
165 | """.stripMargin
166 | }
167 |
168 | protected def batchTimeoutContent(processDuration: Long): String = {
169 | s"""
170 | |$STREAMING_BATCH_ERROR
171 | |
172 | |作业名: $appName
173 | |作业ID: $applicationId
174 | |诊断信息:
175 | | 批次调度运行时长已超阈值(Process Duration: $processDuration ms > Processing Threshold: $batchProcessThreshold ms)
176 | | 单独关闭该类型警报可设置 $BATCH_DELAY_RATIO 的值
177 | """.stripMargin
178 | }
179 |
180 | protected def jobErrorContent(jobErrorReason: String): String = {
181 | s"""
182 | |$STREAMING_JOB_ERROR
183 | |
184 | |作业名: $appName
185 | |作业ID: $applicationId
186 | |诊断信息:
187 | | (长度限制: $failureReasonLimit)
188 | | ${getLimitedFailureReason(jobErrorReason)}
189 | | 单独关闭该类型警报可设置$JOB_NOTICE_ENABLE=false
190 | """.stripMargin
191 | }
192 |
193 | protected def appFailContent(startTime: String, endTime: String): String = {
194 | s"""
195 | |$STREAMING_APPLICATION_ERROR
196 | |
197 | |作业名: $appName
198 | |作业ID: $applicationId
199 | |启动时间: $startTime
200 | |停止时间: $endTime
201 | |
202 | |诊断信息:
203 | | [SparkContext]终止与[StreamingContext]之前,尝试终止StreamingContext
204 | | 如进程无法正常退出,可尝试设置$FORCE_EXIT,强制退出(不推荐)
205 | """.stripMargin
206 | }
207 | }
208 |
209 | object StreamingAlarmListener {
210 | // The length limit for failureReason to sent
211 | val failureReasonLimit: Int = 200
212 | val maxLengthFactor: Double = 1.5
213 |
214 | def getLimitedFailureReason(failureReason: String): String = {
215 | val nextLineIndex = failureReason.indexOf("\n", failureReasonLimit)
216 | if (nextLineIndex == -1) {
217 | failureReason.substring(0, math.min(failureReason.length, (failureReasonLimit * maxLengthFactor).toInt))
218 | } else {
219 | failureReason.substring(0, math.min(nextLineIndex, (failureReasonLimit * maxLengthFactor).toInt))
220 | }
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/streaming-alarmer/src/main/scala/com/netease/spark/alarm/alarmer/streaming/package.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.alarmer
2 |
3 | package object streaming {
4 | private val PREFIX = "spark.alarm.streaming."
5 |
6 | val FORCE_EXIT: String = PREFIX + "application.force.exit"
7 | val BATCH_NOTICE_ENABLE: String = PREFIX + "batch.error.enable"
8 | val BATCH_DELAY_RATIO: String = PREFIX + "batch.delay.ratio"
9 | val BATCH_PROCESS_TIME: String = PREFIX + "batch.process.time.threshold"
10 | val JOB_NOTICE_ENABLE: String = PREFIX + "job.error.enable"
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/streaming-alarmer/src/test/scala/com/netease/spark/alarm/alarmer/streaming/EmailStreamingAlarmListenerTest.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.alarmer.streaming
2 |
3 | import com.netease.spark.alarm.email._
4 | import org.apache.spark.{SparkConf, SparkContext}
5 | import org.apache.spark.streaming._
6 | import org.scalatest.FunSuite
7 |
8 | class EmailStreamingAlarmListenerTest extends FunSuite {
9 | private val conf = new SparkConf(true)
10 | .setMaster("local[2]")
11 | .setAppName("smilodon")
12 | .set(HOSTNAME, "smtp-mail.outlook.com")
13 | .set(SMTP_PORT, "587")
14 | .set(FROM, "yaooqinn@hotmail.com")
15 | .set(SSL_ON_CONNECT, "true")
16 | .set(USERNAME, "hzyaoqin")
17 | .set(PASSWORD, "***")
18 | .set(TO, "yaooqinn@hotmail.com")
19 |
20 |
21 | ignore("on application end") {
22 | val input = Seq(Seq(1), Seq(2), Seq(3), Seq(4))
23 | val sc = new SparkContext(conf)
24 | val ssc = new StreamingContext(sc, Durations.seconds(1))
25 | val listener = new EmailStreamingAlarmListener(sc.getConf)
26 | sc.addSparkListener(listener)
27 | val stream = new AlarmTestInputStream[Int](ssc, input, 2)
28 | stream.foreachRDD(_ => {}) // Dummy output stream
29 | ssc.start()
30 | sc.stop()
31 | assert(ssc.getState() === StreamingContextState.ACTIVE)
32 | }
33 |
34 | ignore("on application end with extra listener") {
35 | conf.set("spark.extraListeners", classOf[EmailStreamingAlarmListener].getName)
36 | val input = Seq(Seq(1), Seq(2), Seq(3), Seq(4))
37 | val sc = new SparkContext(conf)
38 | val ssc = new StreamingContext(sc, Durations.seconds(1))
39 | val stream = new AlarmTestInputStream[Int](ssc, input, 2)
40 | stream.foreachRDD(_ => {}) // Dummy output stream
41 | ssc.start()
42 | sc.stop()
43 | assert(ssc.getState() === StreamingContextState.ACTIVE)
44 | }
45 |
46 | ignore("on batch completed") {
47 | conf.set("spark.extraListeners", classOf[EmailStreamingAlarmListener].getName)
48 | val input = Seq(Seq(1), Seq(2), Seq(3), Seq(4))
49 | val sc = new SparkContext(conf)
50 | val ssc = new StreamingContext(sc, Seconds(1))
51 | val stream = new AlarmTestInputStream[Int](ssc, input, 2)
52 | stream.foreachRDD(_ => {}) // Dummy output stream
53 | ssc.start()
54 |
55 | Thread.sleep(2000)
56 | ssc.stop(stopSparkContext = true, stopGracefully = true)
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/streaming-alarmer/src/test/scala/com/netease/spark/alarm/alarmer/streaming/SmilodonStreamingAlarmListenerTest.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.alarmer.streaming
2 |
3 | import org.apache.spark.{SparkConf, SparkContext}
4 | import org.apache.spark.streaming._
5 | import org.scalatest.FunSuite
6 |
7 | class SmilodonStreamingAlarmListenerTest extends FunSuite {
8 |
9 | val conf: SparkConf = new SparkConf(loadDefaults = true)
10 | .setMaster("local[2]")
11 | .setAppName("smilodon")
12 | .set("spark.alarm.smilodon.users", "yaooqin@hotmail.com")
13 | .set("spark.alarm.smilodon.channels", "POPO,STONE")
14 | // .set("spark.alarm.alarm.force.exit.on.stop", "true")
15 |
16 | ignore("on application end") {
17 | val input = Seq(Seq(1), Seq(2), Seq(3), Seq(4))
18 | val sc = new SparkContext(conf)
19 | val ssc = new StreamingContext(sc, Durations.seconds(1))
20 | val listener = new SmilodonStreamingAlarmListener(sc.getConf)
21 | sc.addSparkListener(listener)
22 | val stream = new AlarmTestInputStream[Int](ssc, input, 2)
23 | stream.foreachRDD(_ => {}) // Dummy output stream
24 | ssc.start()
25 | sc.stop()
26 | assert(ssc.getState() === StreamingContextState.ACTIVE)
27 | }
28 |
29 |
30 | ignore("on application end with extra listener") {
31 | conf.set("spark.extraListeners", classOf[SmilodonStreamingAlarmListener].getName)
32 | val input = Seq(Seq(1), Seq(2), Seq(3), Seq(4))
33 | val sc = new SparkContext(conf)
34 | val ssc = new StreamingContext(sc, Durations.seconds(1))
35 | val stream = new AlarmTestInputStream[Int](ssc, input, 2)
36 | stream.foreachRDD(_ => {}) // Dummy output stream
37 | ssc.start()
38 | sc.stop()
39 | assert(ssc.getState() === StreamingContextState.ACTIVE)
40 | }
41 |
42 | ignore("on batch completed") {
43 | conf.set("spark.extraListeners", classOf[SmilodonStreamingAlarmListener].getName)
44 | .set("spark.alarm.streaming.batch.process.time.threshold", "10ms")
45 | val input = Seq(Seq(1), Seq(2), Seq(3), Seq(4))
46 | val sc = new SparkContext(conf)
47 | val ssc = new StreamingContext(sc, Seconds(1))
48 | val stream = new AlarmTestInputStream[Int](ssc, input, 2)
49 | stream.foreachRDD(_ => {}) // Dummy output stream
50 | ssc.start()
51 |
52 | Thread.sleep(2000)
53 | ssc.stop(stopSparkContext = true, stopGracefully = true)
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/streaming-alarmer/src/test/scala/com/netease/spark/alarm/alarmer/streaming/StreamingAlarmListenerTest.scala:
--------------------------------------------------------------------------------
1 | package com.netease.spark.alarm.alarmer.streaming
2 |
3 | import org.scalatest.FunSuite
4 |
5 | import scala.reflect.runtime.{universe => ru}
6 |
7 | class StreamingAlarmListenerTest extends FunSuite {
8 | import StreamingAlarmListener._
9 |
10 | test("test get limit failure reason") {
11 | val className = "com.netease.spark.alarm.alarmer.streaming.StreamingAlarmListener"
12 | val limitFieldName = "failureReasonLimit"
13 | val factorFieldName = "maxLengthFactor"
14 | val lengthLimit = 10
15 | setStaticFieldValue(className, limitFieldName, lengthLimit)
16 | // length = 0
17 | val reason0 = ""
18 | // length < limit
19 |
20 | val reason1 = "Failed"
21 | // length > limit
22 | val reason2 = "Task Failed with Exception"
23 | // length > limit with multi lines not than lengthLimit * maxLengthFactor
24 | val reason3 = "Task Failed with Exception\n\tline2"
25 |
26 | setStaticFieldValue(className, factorFieldName, 10.0)
27 | assert(getLimitedFailureReason(reason0) === reason0)
28 | assert(getLimitedFailureReason(reason1) === reason1)
29 | assert(getLimitedFailureReason(reason2) === reason2)
30 | assert(getLimitedFailureReason(reason3) === reason3.split("\n")(0))
31 |
32 | setStaticFieldValue(className, factorFieldName, 1.1)
33 | assert(getLimitedFailureReason(reason2) === reason2.substring(0, (maxLengthFactor * failureReasonLimit).toInt))
34 | }
35 |
36 | def setStaticFieldValue(className: String, fieldName: String, value: Any): Unit = {
37 | try {
38 | val mirror = ru.runtimeMirror(getClass.getClassLoader)
39 | val moduleSymbol = mirror.staticModule(className)
40 | val moduleMirror = mirror.reflectModule(moduleSymbol)
41 | val instanceMirror = mirror.reflect(moduleMirror.instance)
42 | val limitField = moduleSymbol.typeSignature.decl(ru.TermName(fieldName))
43 | val limit = instanceMirror.reflectField(limitField.asTerm)
44 | limit.set(value)
45 | } catch {
46 | case e: Exception => e.printStackTrace()
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/streaming-alarmer/src/test/scala/org/apache/spark/streaming/AlarmTestInputStream.scala:
--------------------------------------------------------------------------------
1 | package org.apache.spark.streaming
2 |
3 | import org.apache.spark.rdd.RDD
4 | import org.apache.spark.streaming.dstream.InputDStream
5 | import org.apache.spark.streaming.scheduler.StreamInputInfo
6 |
7 | import scala.reflect.ClassTag
8 |
9 | /**
10 | * This is a input stream just for the testsuites. This is equivalent to a checkpointable,
11 | * replayable, reliable message queue like Kafka. It requires a sequence as input, and
12 | * returns the i_th element at the i_th batch under manual clock.
13 | */
14 | class AlarmTestInputStream[T: ClassTag](_ssc: StreamingContext, input: Seq[Seq[T]], numPartitions: Int)
15 | extends InputDStream[T](_ssc) {
16 |
17 | def start() {}
18 |
19 | def stop() {}
20 |
21 | def compute(validTime: Time): Option[RDD[T]] = {
22 | logInfo("Computing RDD for time " + validTime)
23 | val index = ((validTime - zeroTime) / slideDuration - 1).toInt
24 | val selectedInput = if (index < input.size) input(index) else Seq[T]()
25 |
26 | // lets us test cases where RDDs are not created
27 | if (selectedInput == null) {
28 | return None
29 | }
30 |
31 | // Report the input data's information to InputInfoTracker for testing
32 | val inputInfo = StreamInputInfo(id, selectedInput.length.toLong)
33 | ssc.scheduler.inputInfoTracker.reportInfo(validTime, inputInfo)
34 |
35 | val rdd = ssc.sc.makeRDD(selectedInput, numPartitions)
36 | logInfo("Created RDD " + rdd.id + " with " + selectedInput)
37 | Some(rdd)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------