blob: f69fac374ba7258b0fadb066c813f3ec33a9ef94 [file] [log] [blame]
#!/usr/bin/python
# Copyright 2015 Google Inc. All Rights Reserved.
#
# 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.
"""JsonPoll reads a response from a socket and writes it to disk."""
__author__ = 'cgibson@google.com (Chris Gibson)'
import errno
import json
import os
import socket
import sys
import tempfile
import textwrap
import time
import urllib2
import options
optspec = """
jsonpoll [options]
--
host= host to connect to [localhost]
port= port to connect to [8080]
i,interval= poll interval in seconds [15]
"""
class JsonPoll(object):
"""Periodically poll a web server to request stats."""
# The directory that JSON files will be written to. Note that changing this
# path to another filesystem other than /tmp will affect the atomicity of the
# os.rename() when moving files into the final destination.
OUTPUT_DIR = '/tmp/glaukus/'
# The time to wait before giving up on blocking connection operations.
_SOCKET_TIMEOUT_SECS = 15
def __init__(self, host, port, interval):
self.hostport = 'http://%s:%d' % (host, port)
# The time to wait between requests in seconds.
self.poll_interval_secs = interval
self.api_modem_output_file = os.path.join(self.OUTPUT_DIR, 'modem.json')
self.api_radio_output_file = os.path.join(self.OUTPUT_DIR, 'radio.json')
self.paths_to_statfiles = {'api/modem': self.api_modem_output_file,
'api/radio': self.api_radio_output_file}
self.last_response = None
def WriteToStderr(self, msg, is_json=False):
"""Write a message to stderr."""
if is_json:
# Make the json easier to parse from the logs.
json_data = json.loads(msg)
json_str = json.dumps(json_data, sort_keys=True, indent=2,
separators=(',', ': '))
# Logging pretty-printed json is like logging one huge line. Logos is
# configured to limit lines to 768 characters. Split the logged output at
# half of that to make sure logos doesn't clip our output.
sys.stderr.write('\n'.join(textwrap.wrap(json_str, width=384)))
sys.stderr.flush()
else:
sys.stderr.write(msg)
sys.stderr.flush()
def RequestStats(self):
"""Sends a request via HTTP GET to the specified web server."""
for path, output_file in self.paths_to_statfiles.iteritems():
url = '%s/%s' % (self.hostport, path)
tmpfile = ''
try:
response = self.GetHttpResponse(url)
if not response:
self.WriteToStderr('Failed to get response from glaukus: %s', url)
continue
elif self.last_response == response:
self.WriteToStderr('Skipping file write as content has not changed.')
continue
self.last_response = response
with tempfile.NamedTemporaryFile(delete=False) as fd:
if not self.CreateDirs(os.path.dirname(output_file)):
self.WriteToStderr('Failed to create output directory: %s' %
os.path.dirname(output_file))
continue
tmpfile = fd.name
fd.write(response)
fd.flush()
os.fsync(fd.fileno())
try:
os.rename(tmpfile, output_file)
except OSError as ex:
self.WriteToStderr('Failed to move %s to %s: %s' % (
tmpfile, output_file, ex))
continue
finally:
if os.path.exists(tmpfile):
os.unlink(tmpfile)
def GetHttpResponse(self, url):
"""Creates a request and retrieves the response from a web server."""
try:
handle = urllib2.urlopen(url, timeout=self._SOCKET_TIMEOUT_SECS)
response = handle.read()
except socket.timeout as ex:
self.WriteToStderr('Connection to %s timed out after %d seconds: %s'
% (url, self._SOCKET_TIMEOUT_SECS, ex))
return None
except urllib2.URLError as ex:
self.WriteToStderr('Connection to %s failed: %s' % (url, ex.reason))
return None
# Write the response to stderr so it will be uploaded with the other system
# log files. This will allow turbogrinder to alert on the radio subsystem.
self.WriteToStderr(response, is_json=True)
return response
def CreateDirs(self, dir_to_create):
"""Recursively creates directories."""
try:
os.makedirs(dir_to_create)
except os.error as ex:
if ex.errno == errno.EEXIST:
return True
self.WriteToStderr('Failed to create directory: %s' % ex)
return False
return True
def RunForever(self):
while True:
self.RequestStats()
time.sleep(self.poll_interval_secs)
def main():
o = options.Options(optspec)
(opt, unused_flags, unused_extra) = o.parse(sys.argv[1:])
poller = JsonPoll(opt.host, opt.port, opt.interval)
poller.RunForever()
if __name__ == '__main__':
main()