Skip to content

Instantly share code, notes, and snippets.

@antdking
Created April 26, 2015 00:17
Show Gist options
  • Save antdking/9c9adef6f2a3fd3fbda9 to your computer and use it in GitHub Desktop.
Save antdking/9c9adef6f2a3fd3fbda9 to your computer and use it in GitHub Desktop.
{
"Google": {
"url": "https://google.co.uk"
}
}
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()
<!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>
# 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