Files
dragonpilot/selfdrive/loggerd/uploader.py

276 lines
7.7 KiB
Python
Raw Normal View History

2019-10-09 18:43:53 +00:00
#!/usr/bin/env python3
2016-11-29 18:34:21 -08:00
import os
2019-01-23 15:34:52 -08:00
import re
2016-11-29 18:34:21 -08:00
import time
2017-05-11 12:41:17 -07:00
import json
2016-11-29 18:34:21 -08:00
import random
import ctypes
import inspect
import requests
import traceback
import threading
2017-12-23 17:15:27 -08:00
import subprocess
2016-11-29 18:34:21 -08:00
from selfdrive.swaglog import cloudlog
2017-05-11 12:41:17 -07:00
from selfdrive.loggerd.config import ROOT
2016-11-29 18:34:21 -08:00
2017-05-11 12:41:17 -07:00
from common.params import Params
2019-08-13 01:36:45 +00:00
from common.api import Api
2016-11-29 18:34:21 -08:00
2017-01-11 13:07:55 -08:00
fake_upload = os.getenv("FAKEUPLOAD") is not None
2016-11-29 18:34:21 -08:00
def raise_on_thread(t, exctype):
for ctid, tobj in threading._active.items():
if tobj is t:
tid = ctid
break
else:
raise Exception("Could not find thread")
'''Raises an exception in the threads with id tid'''
if not inspect.isclass(exctype):
raise TypeError("Only types can be raised (not instances)")
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid),
ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
# "if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect"
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
raise SystemError("PyThreadState_SetAsyncExc failed")
2019-10-09 18:43:53 +00:00
def get_directory_sort(d):
return list(map(lambda s: s.rjust(10, '0'), d.rsplit('--', 1)))
2016-11-29 18:34:21 -08:00
2019-10-09 18:43:53 +00:00
def listdir_by_creation(d):
try:
paths = os.listdir(d)
paths = sorted(paths, key=get_directory_sort)
return paths
except OSError:
cloudlog.exception("listdir_by_creation failed")
return list()
2016-11-29 18:34:21 -08:00
def clear_locks(root):
for logname in os.listdir(root):
path = os.path.join(root, logname)
try:
for fname in os.listdir(path):
if fname.endswith(".lock"):
os.unlink(os.path.join(path, fname))
except OSError:
cloudlog.exception("clear_locks failed")
2017-12-23 17:15:27 -08:00
def is_on_wifi():
# ConnectivityManager.getActiveNetworkInfo()
2018-06-25 13:48:52 -07:00
try:
2019-10-09 18:43:53 +00:00
result = subprocess.check_output(["service", "call", "connectivity", "2"], encoding='utf8').strip().split("\n") # pylint: disable=unexpected-keyword-arg
2018-06-25 13:48:52 -07:00
except subprocess.CalledProcessError:
return False
2019-10-09 18:43:53 +00:00
# Concatenate all ascii parts
r = ""
for line in result[1:]:
r += line[51:67]
2017-12-23 17:15:27 -08:00
2019-10-09 18:43:53 +00:00
return "W.I.F.I" in r
2017-12-23 17:15:27 -08:00
2019-01-23 15:34:52 -08:00
def is_on_hotspot():
try:
2019-10-09 18:43:53 +00:00
result = subprocess.check_output(["ifconfig", "wlan0"], encoding='utf8') # pylint: disable=unexpected-keyword-arg
2019-01-23 15:34:52 -08:00
result = re.findall(r"inet addr:((\d+\.){3}\d+)", result)[0][0]
is_android = result.startswith('192.168.43.')
is_ios = result.startswith('172.20.10.')
is_entune = result.startswith('10.0.2.')
return (is_android or is_ios or is_entune)
2019-01-23 15:34:52 -08:00
except:
return False
2016-11-29 18:34:21 -08:00
2019-10-09 18:43:53 +00:00
class Uploader():
2019-09-09 23:03:02 +00:00
def __init__(self, dongle_id, root):
2016-11-29 18:34:21 -08:00
self.dongle_id = dongle_id
2019-09-09 23:03:02 +00:00
self.api = Api(dongle_id)
2016-11-29 18:34:21 -08:00
self.root = root
self.upload_thread = None
self.last_resp = None
self.last_exc = None
2019-10-31 17:14:40 -07:00
self.immediate_priority = {"qlog.bz2": 0, "qcamera.ts": 1}
2019-10-09 18:43:53 +00:00
self.high_priority = {"rlog.bz2": 0, "fcamera.hevc": 1, "dcamera.hevc": 2}
2016-11-29 18:34:21 -08:00
def clean_dirs(self):
try:
for logname in os.listdir(self.root):
path = os.path.join(self.root, logname)
# remove empty directories
if not os.listdir(path):
os.rmdir(path)
except OSError:
cloudlog.exception("clean_dirs failed")
2019-10-09 18:43:53 +00:00
def get_upload_sort(self, name):
if name in self.immediate_priority:
return self.immediate_priority[name]
if name in self.high_priority:
return self.high_priority[name] + 100
return 1000
2016-11-29 18:34:21 -08:00
def gen_upload_files(self):
2016-12-12 17:47:46 -08:00
if not os.path.isdir(self.root):
return
2019-10-09 18:43:53 +00:00
for logname in listdir_by_creation(self.root):
2016-11-29 18:34:21 -08:00
path = os.path.join(self.root, logname)
2019-05-16 13:20:29 -07:00
try:
names = os.listdir(path)
except OSError:
continue
2016-11-29 18:34:21 -08:00
if any(name.endswith(".lock") for name in names):
continue
2019-10-09 18:43:53 +00:00
for name in sorted(names, key=self.get_upload_sort):
2016-11-29 18:34:21 -08:00
key = os.path.join(logname, name)
fn = os.path.join(path, name)
yield (name, key, fn)
2019-06-28 21:11:30 +00:00
def next_file_to_upload(self, with_raw):
2019-10-09 18:43:53 +00:00
upload_files = list(self.gen_upload_files())
2019-06-28 21:11:30 +00:00
# try to upload qlog files first
2019-10-09 18:43:53 +00:00
for name, key, fn in upload_files:
if name in self.immediate_priority:
return (key, fn)
2019-06-28 21:11:30 +00:00
if with_raw:
2019-09-09 23:03:02 +00:00
# then upload the full log files, rear and front camera files
2019-10-09 18:43:53 +00:00
for name, key, fn in upload_files:
if name in self.high_priority:
return (key, fn)
2017-07-28 01:24:39 -07:00
2017-12-23 17:15:27 -08:00
# then upload other files
2019-10-09 18:43:53 +00:00
for name, key, fn in upload_files:
2017-12-23 17:15:27 -08:00
if not name.endswith('.lock') and not name.endswith(".tmp"):
2019-10-09 18:43:53 +00:00
return (key, fn)
2016-11-29 18:34:21 -08:00
return None
def do_upload(self, key, fn):
try:
2019-08-13 01:36:45 +00:00
url_resp = self.api.get("v1.3/"+self.dongle_id+"/upload_url/", timeout=10, path=key, access_token=self.api.get_token())
2017-05-11 12:41:17 -07:00
url_resp_json = json.loads(url_resp.text)
url = url_resp_json['url']
headers = url_resp_json['headers']
2019-08-13 01:36:45 +00:00
cloudlog.info("upload_url v1.3 %s %s", url, str(headers))
2016-11-29 18:34:21 -08:00
2017-01-11 13:07:55 -08:00
if fake_upload:
2017-06-28 13:57:09 -07:00
cloudlog.info("*** WARNING, THIS IS A FAKE UPLOAD TO %s ***" % url)
2019-10-09 18:43:53 +00:00
class FakeResponse():
2017-01-11 13:07:55 -08:00
def __init__(self):
self.status_code = 200
self.last_resp = FakeResponse()
else:
with open(fn, "rb") as f:
2018-04-14 06:10:58 +00:00
self.last_resp = requests.put(url, data=f, headers=headers, timeout=10)
2016-11-29 18:34:21 -08:00
except Exception as e:
self.last_exc = (e, traceback.format_exc())
raise
def normal_upload(self, key, fn):
self.last_resp = None
self.last_exc = None
try:
self.do_upload(key, fn)
except Exception:
pass
return self.last_resp
def upload(self, key, fn):
2016-11-29 18:34:21 -08:00
try:
sz = os.path.getsize(fn)
except OSError:
cloudlog.exception("upload: getsize failed")
return False
cloudlog.event("upload", key=key, fn=fn, sz=sz)
cloudlog.info("checking %r with size %r", key, sz)
if sz == 0:
# can't upload files of 0 size
os.unlink(fn) # delete the file
success = True
else:
cloudlog.info("uploading %r", fn)
stat = self.normal_upload(key, fn)
2017-05-22 22:26:12 -07:00
if stat is not None and stat.status_code in (200, 201):
2016-11-29 18:34:21 -08:00
cloudlog.event("upload_success", key=key, fn=fn, sz=sz)
2019-06-28 21:11:30 +00:00
# delete the file
try:
os.unlink(fn)
except OSError:
2019-07-22 19:17:47 +00:00
cloudlog.event("delete_failed", stat=stat, exc=self.last_exc, key=key, fn=fn, sz=sz)
2019-06-28 21:11:30 +00:00
2016-11-29 18:34:21 -08:00
success = True
else:
cloudlog.event("upload_failed", stat=stat, exc=self.last_exc, key=key, fn=fn, sz=sz)
success = False
self.clean_dirs()
return success
def uploader_fn(exit_event):
cloudlog.info("uploader_fn")
2017-05-11 12:41:17 -07:00
params = Params()
2019-10-09 18:43:53 +00:00
dongle_id = params.get("DongleId").decode('utf8')
2017-01-09 20:59:00 -08:00
2019-09-09 23:03:02 +00:00
if dongle_id is None:
cloudlog.info("uploader missing dongle_id")
raise Exception("uploader can't start without dongle id")
2017-01-09 20:59:00 -08:00
2019-09-09 23:03:02 +00:00
uploader = Uploader(dongle_id, ROOT)
2016-11-29 18:34:21 -08:00
2018-02-10 09:31:56 -06:00
backoff = 0.1
2016-11-29 18:34:21 -08:00
while True:
2019-10-09 18:43:53 +00:00
allow_raw_upload = (params.get("IsUploadRawEnabled") != b"0")
allow_cellular = (params.get("IsUploadVideoOverCellularEnabled") != b"0")
2019-01-23 15:34:52 -08:00
on_hotspot = is_on_hotspot()
on_wifi = is_on_wifi()
should_upload = allow_cellular or (on_wifi and not on_hotspot)
2017-12-23 17:15:27 -08:00
2018-02-10 09:31:56 -06:00
if exit_event.is_set():
return
2016-11-29 18:34:21 -08:00
2019-06-28 21:11:30 +00:00
d = uploader.next_file_to_upload(with_raw=allow_raw_upload and should_upload)
2018-02-10 09:31:56 -06:00
if d is None:
time.sleep(5)
continue
2016-11-29 18:34:21 -08:00
2019-10-09 18:43:53 +00:00
key, fn = d
2016-11-29 18:34:21 -08:00
2019-01-23 15:34:52 -08:00
cloudlog.event("uploader_netcheck", allow_cellular=allow_cellular, is_on_hotspot=on_hotspot, is_on_wifi=on_wifi)
2018-02-10 09:31:56 -06:00
cloudlog.info("to upload %r", d)
success = uploader.upload(key, fn)
if success:
backoff = 0.1
else:
cloudlog.info("backoff %r", backoff)
time.sleep(backoff + random.uniform(0, backoff))
backoff = min(backoff*2, 120)
cloudlog.info("upload done, success=%r", success)
2016-11-29 18:34:21 -08:00
def main(gctx=None):
uploader_fn(threading.Event())
if __name__ == "__main__":
main()