Created
April 26, 2015 00:17
-
-
Save antdking/9c9adef6f2a3fd3fbda9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"Google": { | |
"url": "https://google.co.uk" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import socket | |
class FlockAcquireError(BaseException): | |
pass | |
class FlockReleaseError(BaseException): | |
pass | |
class Flock(object): | |
"""Class to handle creating and removing (pid) lockfiles""" | |
pddr = lambda self, lock: ('<%s %s@%s>' % | |
(self.path, lock['pid'], lock['host'])) | |
def __init__(self, path, debug=False): | |
self.pid = os.getpid() | |
self.host = socket.gethostname() | |
self.path = path | |
self._debug = debug # set this to get status messages | |
self.addr = '%d@%s' % (self.pid, self.host) | |
self.fddr = '<%s %s>' % (self.path, self.addr) | |
if debug: | |
self.debug = lambda *a, **kw: print(*a, **kw) | |
else: | |
self.debug = lambda *a, **kw: None | |
def acquire(self): | |
"""Acquire a lock, returning self if successful, False otherwise""" | |
if self.is_locked(): | |
if self._debug: | |
lock = self._read_lock() | |
self.debug('Previous lock detected: %s' % self.pddr(lock)) | |
return False | |
try: | |
fh = open(self.path, 'w') | |
fh.write(self.addr) | |
fh.close() | |
self.debug('Acquired lock: %s' % self.fddr) | |
except: | |
try: | |
os.unlink(self.path) | |
except: | |
pass | |
raise (FlockAcquireError, | |
'Error acquiring lock: %s' % self.fddr) | |
return self | |
def release(self): | |
"""Release lock, returning self""" | |
if self.own_lock(): | |
try: | |
os.unlink(self.path) | |
self.debug('Released lock: %s' % self.fddr) | |
except: | |
raise (FlockReleaseError, | |
'Error releasing lock: %s' % self.fddr) | |
return self | |
def _read_lock(self): | |
"""Internal method to read lock info""" | |
try: | |
lock = {} | |
fh = open(self.path) | |
data = fh.read().rstrip().split('@') | |
fh.close() | |
lock['pid'], lock['host'] = data | |
return lock | |
except: | |
return {'pid': 8 ** 10, 'host': ''} | |
def is_locked(self): | |
"""Check if we already have a lock""" | |
try: | |
lock = self._read_lock() | |
os.kill(int(lock['pid']), 0) | |
return lock['host'] == self.host | |
except: | |
return False | |
def own_lock(self): | |
"""Check if we own the lock""" | |
lock = self._read_lock() | |
return self.fddr == self.pddr(lock) | |
def __del__(self): | |
"""Magic method to clean up lock when program exits""" | |
self.release() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Heartbeat monitor</title> | |
<meta charset="utf-8"> | |
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"> | |
<style> | |
body { | |
font-family: Verdana,serif; | |
} | |
.status { | |
position: absolute; | |
left: 200px; | |
} | |
[data-status='0']:after { | |
content: '[OK]'; | |
color: green; | |
} | |
[data-status='1']:after { | |
content: '[ERROR]'; | |
color: orange; | |
} | |
[data-status='2']:after { | |
content: '[DOWN]'; | |
color: red; | |
} | |
</style> | |
</head> | |
<body> | |
<h3>Insomnia 24/7</h3> | |
<div id="content"></div> | |
<script type="application/javascript"> | |
"use strict"; | |
window.onload = function() { | |
var client = new XMLHttpRequest(); | |
client.open('GET', '/json', true); | |
client.onload = function () { | |
if (client.status == 200) { | |
if (!client.responseText) return; | |
var data = JSON.parse(client.responseText); | |
var div = document.getElementById('content'); | |
for (var key in data) { | |
if (!key) continue; | |
var row = '<div><span>' + key + '</span>'; | |
row += '<span class="status" data-status="'; | |
row += data[key]['status'] + '"></div>'; | |
div.innerHTML += row; | |
} | |
} | |
}; | |
client.send(); | |
} | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright 2015 cybojenix <[email protected]> | |
# | |
# 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. | |
import asyncio | |
import json | |
import time | |
import aiohttp | |
import aiohttp.web | |
from flock import Flock, FlockAcquireError, FlockReleaseError | |
DEBUG = False | |
LOCK_FILE = '.inso_heartbeat.lock' | |
DATA_FILE = '.inso_heartbeat.json' | |
SERVER_FILE = '.inso_heartbeat.servers.json' | |
TIMEOUT = 20 | |
FLOCK = Flock(LOCK_FILE, debug=DEBUG) | |
ALOCK = asyncio.Lock() | |
LOOP = asyncio.get_event_loop() | |
LAST_RUN = 0 | |
PAGE_CONTENT = open('heartbeat.html', 'rb').read() | |
def get_lock(): | |
try: | |
return FLOCK.acquire() | |
except FlockAcquireError: | |
return False | |
def cleanup(*args, needs_cleanup=True): | |
if not needs_cleanup: | |
return | |
try: | |
FLOCK.release() | |
except FlockReleaseError: | |
pass | |
@asyncio.coroutine | |
def process_request(hostname, url, pattern, method='GET'): | |
with (yield from ALOCK): | |
try: | |
d = json.load(open(DATA_FILE)) | |
except (OSError, ValueError): | |
d = {} | |
d[hostname] = d.get(hostname, {}) | |
last_run = d[hostname].get('time', 0) | |
if time.time() - last_run < 300: | |
return | |
d[hostname]['running'] = True | |
json.dump(d, open(DATA_FILE, 'w')) | |
try: | |
r = yield from asyncio.wait_for(aiohttp.request(method, url), TIMEOUT) | |
except asyncio.TimeoutError: | |
status = 2 | |
else: | |
s = r.status | |
status = 1 if s >= 400 else 0 | |
# raw = yield from r.text() | |
# status = raw == pattern # TODO: proper pattern matching? | |
run_time = int(time.time()) | |
hn = { | |
'processing': False, | |
'status': status, | |
'time': run_time | |
} | |
with (yield from ALOCK): | |
try: | |
d = json.load(open(DATA_FILE)) | |
except (OSError, ValueError): | |
d = {} | |
d[hostname] = hn | |
json.dump(d, open(DATA_FILE, 'w')) | |
@asyncio.coroutine | |
def run_requests(): | |
global LAST_RUN | |
LAST_RUN = time.time() | |
subtasks = [] | |
servers = json.load(open(SERVER_FILE)) | |
for hn, obj in servers.items(): | |
subtasks.append(asyncio.async( | |
process_request(hn, obj['url'], obj.get('pattern')))) | |
yield from asyncio.wait(subtasks) | |
cleanup() | |
@asyncio.coroutine | |
def page(request): | |
return aiohttp.web.Response(body=PAGE_CONTENT) | |
@asyncio.coroutine | |
def view(request): | |
global LAST_RUN | |
if time.time() - LAST_RUN > 300: | |
if get_lock() is not False: | |
asyncio.async(run_requests()) | |
with (yield from ALOCK): | |
return aiohttp.web.Response(body=open(DATA_FILE, 'rb').read()) | |
def main(): | |
app = aiohttp.web.Application() | |
app.router.add_route('GET', '/', page) | |
app.router.add_route('GET', '/json', view) | |
f = LOOP.create_server(app.make_handler(), '0.0.0.0', 8081) | |
srv = LOOP.run_until_complete(f) | |
print('serving on', srv.sockets[0].getsockname()) | |
LOOP.run_forever() | |
if __name__ == '__main__': | |
try: | |
main() | |
finally: # we need to release the lock no matter what at the end | |
LOOP.close() | |
cleanup() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment