Skip to content

Commit

Permalink
Added TcpConnectProbe
Browse files Browse the repository at this point in the history
Corrected definition of the responseTime attribute of HttpProbe
Updated README
  • Loading branch information
mwittig committed Apr 19, 2015
1 parent b82da48 commit ab78f0c
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 21 deletions.
36 changes: 31 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# pimatic-probe

A pimatic plugin to probe HTTP(S), TCP or UDP services
A pimatic plugin to probe HTTP(S), TCP and UDP services.

NOTE: Currently, this a basic implementation of the HTTP(S) probe is provided, only!
Note: UDP is currently not supported and will be added at later stage!

## Configuration

Expand All @@ -17,10 +17,13 @@ purposes you may set property `debug` to true. This will write additional debug
Then you need to add a device in the `devices` section. Currently, only the following device type is supported:

* HttpProbe: This type provides a probe for pinging HTTP/HTTPS services

As part of the device definition you need to provide the `url` for the Web Service to be pinged. If the property
* TcpConnectProbe: This type provides a probe for TCP connect

### HttpProbe Configuration

As part of the device definition you need to provide the `url` for the Web Service to be probed. If the property
`enableResponseTime` is set to true (false by default) the device will additionally expose a `responseTime` attribute,
which allows for monitoring the response times over time. You may also set the `interval` property for the probing
which allows for monitoring the response times. You may also set the `interval` property for the probing
interval in seconds (60 seconds by default). **Warning Notice: Generally, it is not advised to ping external services
at a high frequency as this may be regarded as a denial-of-service attack!**

Expand All @@ -32,6 +35,25 @@ As part of the device definition you need to provide the `url` for the Web Servi
"enableResponseTime": false
"interval": 60
}

### TcpConnectProbe Configuration

As part of the device definition you need to provide the `host` and `port`for the TCP Service to be probed. If the
property `enableConnectTime` is set to true (false by default) the device will additionally expose a `connectTime`
attribute, which allows for monitoring the connection establishment times. You may also set the `interval` property
for the probing interval in seconds (60 seconds by default). The `timeout` property may be set the timeout in
seconds (10 seconds by default) for inactivity on the TCP socket.

{
"id": "probe2",
"class": "TcpConnectProbe",
"name": "Call Monitor",
"host": "fritz.box",
"port": 1012,
"enableConnectTime": false,
"interval": 10,
"timeout": 10
}

## History

Expand All @@ -40,4 +62,8 @@ As part of the device definition you need to provide the `url` for the Web Servi
* 20150419, V0.0.2
* Fixed HTTP request termination to make sure HTTP connection gets closed right away
* Added optional ``responseTime`` attribute
* Updated README
* 20150420, V0.0.3
* Added TcpConnectProbe
* Corrected definition of the responseTime attribute of HttpProbe
* Updated README
26 changes: 25 additions & 1 deletion device-config-schema.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,34 @@ module.exports = {
enableResponseTime:
description: "Adds an attribute to monitor response times"
type: "boolean"
default: 60
default: false
interval:
description: "Polling interval for HTTP/HTTPS probes in seconds"
type: "number"
default: 60
},
TcpConnectProbe: {
title: "TCP Connect Probe"
description: "Provides a probe for a TCP connect to the given host and port"
type: "object"
properties:
host:
description: "Hostname or IP address of the server"
type: "string"
port:
description: "Server port"
type: "number"
enableConnectTime:
description: "Adds an attribute to monitor connect times"
type: "boolean"
default: false
interval:
description: "Polling interval for TCP connect probes in seconds"
type: "number"
default: 60
timeout:
description: "Timeout (in seconds) of inactivity on the TCP socket"
type: "number"
default: 10
}
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pimatic-probe",
"description": "A pimatic plugin to probe HTTP(S), TCP or UDP services",
"description": "A pimatic plugin to probe HTTP(S), TCP and UDP services",
"author": {
"name": "Marcus Wittig",
"url": "https://github.com/mwittig/pimatic-probe"
Expand All @@ -13,7 +13,7 @@
"device-config-schema.coffee",
"LICENSE"
],
"version": "0.0.2",
"version": "0.0.3",
"homepage": "https://github.com/mwittig/pimatic-probe",
"keywords": [
"pimatic",
Expand Down
124 changes: 111 additions & 13 deletions probe.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = (env) ->
# Require the nodejs net API
net = require 'net'

dns = require 'dns'
url = require 'url'
http = require 'http'
https = require 'https'
Expand All @@ -25,31 +26,37 @@ module.exports = (env) ->
return new HttpProbeDevice(config, @, @framework, lastState)
})

@framework.deviceManager.registerDeviceClass("TcpConnectProbe", {
configDef: deviceConfigDef.TcpConnectProbe,
createCallback: (config, plugin, @framework, lastState) =>
return new TcpConnectProbeDevice(config, @, @framework, lastState)
})


class HttpProbeDevice extends env.devices.PresenceSensor
# Initialize device by reading entity definition from middleware
constructor: (@config, plugin, @framework, lastState) ->
@debug = plugin.config.debug;
env.logger.debug("ProbeBaseDevice Initialization") if @debug
@debug = plugin.config.debug
env.logger.debug("HttpProbeDevice Initialization") if @debug
@id = config.id
@name = config.name
@responseTime = lastState?.responseTime?.value or 0
@_presence = lastState?.presence?.value or false
@_options = url.parse(config.url, false, true)
@_service = if @_options.protocol is 'https' then https else http
@_service = if @_options.protocol is 'https' then https else http

if config.enableResponseTime
@addAttribute('responseTime', {
description: "Topic Data",
description: "HTTP/HTTPS Response Time",
type: "number"
acronym: "RTT"
unit: "ms"
acronym: "RT"
unit: " ms"
})
@['getResponseTime'] = ()-> Promise.resolve(@responseTime)

if !@_options.hostname?
env.logger.error("URL must contain a hostname")
@deviceConfigurationError = true;
@deviceConfigurationError = true

@interval = 1000 * config.interval
super()
Expand All @@ -76,19 +83,21 @@ module.exports = (env) ->
@_requestUpdate()

_requestUpdate: ->
@_ping().then( =>
@_ping().then(=>
@_setPresence (yes)
).catch((error) =>
env.logger.error("Probe for device id=" + @id + ": failed" + error.toString())
env.logger.error "Probe for device id=" + @id + " failed: " + error
@_setPresence (no)
)

_ping: ->
return new Promise( (resolve, reject) =>
start = Date.now()
return new Promise((resolve, reject) =>
timeStart = process.hrtime()
request = @_service.get(@_options, (response) =>
@_setResponseTime(Number Date.now() - start)
env.logger.debug "Got response status=" + response.statusCode + ", time=" + @responseTime + "ms" if @debug
timeEnd = process.hrtime(timeStart)
time = (timeEnd[0] * 1e9 + timeEnd[1]) / 1e6
@_setResponseTime(Number time.toFixed())
env.logger.debug "Got response , device id=" + @id + ", status=" + response.statusCode + ", time=" + @responseTime + " ms" if @debug
request.abort()
resolve(@responseTime)
).on "error", ((error) =>
Expand All @@ -103,6 +112,95 @@ module.exports = (env) ->
@responseTime = value
@emit "responseTime", value if @config.enableResponseTime

getPresence: ->
@_presence = false if @_presence?
Promise.resolve(@_presence)


class TcpConnectProbeDevice extends env.devices.PresenceSensor
# Initialize device by reading entity definition from middleware
constructor: (@config, plugin, @framework, lastState) ->
@debug = plugin.config.debug
env.logger.debug("TcpConnectProbeDevice Initialization") if @debug
@id = config.id
@name = config.name
@connectTime = lastState?.connectTime?.value or 0
@_presence = lastState?.presence?.value or false
@_port = config.port
@_timeout = config.timeout * 1000

if config.enableConnectTime
@addAttribute('connectTime', {
description: "TCP Connect Time",
type: "number"
acronym: "CT"
unit: " ms"
})
@['getConnectTime'] = ()-> Promise.resolve(@connectTime)

@interval = 1000 * config.interval
super()

dns.lookup(config.host, null, (err, address, family) =>
if err?
env.logger.error "Name Lookup failed: " + error
else
@_host = address
@_scheduleUpdate()
)

# poll device according to interval
_scheduleUpdate: () ->
if typeof @intervalObject isnt 'undefined'
clearInterval(=>
@intervalObject
)

# keep updating
if @interval > 0
@intervalObject = setInterval(=>
@_requestUpdate()
, @interval
)

# perform an update now
@_requestUpdate()

_requestUpdate: ->
@_connect().then(=>
@_setPresence (yes)
).catch((error) =>
env.logger.error "Probe for device id=" + @id + ", host=" + @_host + ", port=" + @_port + " failed: " + error
@_setPresence (no)
)

_connect: ->
return new Promise((resolve, reject) =>
timeStart = process.hrtime()
client = new net.Socket
client.setTimeout(@_timeout, =>
client.destroy()
reject(new Error "TCP connect attempt timed out")
)
client.on "error", ((error) =>
client.destroy()
reject(error)
)
client.connect(@_port, @_host, =>
timeEnd = process.hrtime(timeStart)
time = (timeEnd[0] * 1e9 + timeEnd[1]) / 1e6
@_setConnectTime(Number time.toFixed())
client.destroy()
env.logger.debug "Connected to server, device id=" + @id + ", connectTime=" + @connectTime + " ms" if @debug
resolve(@connectTime)
)
)

_setConnectTime: (value) ->
if @connectTime isnt value
@connectTime = value
@emit "connectTime", value if @config.enableConnectTime

getPresence: ->
@_presence = false if @_presence?
return Promise.resolve @_presence
Expand Down

0 comments on commit ab78f0c

Please sign in to comment.