xref: /openbmc/openbmc/poky/bitbake/lib/bb/event.py (revision fc81e38a)
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3"""
4BitBake 'Event' implementation
5
6Classes and functions for manipulating 'events' in the
7BitBake build tools.
8"""
9
10# Copyright (C) 2003, 2004  Chris Larson
11#
12# This program is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License version 2 as
14# published by the Free Software Foundation.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License along
22# with this program; if not, write to the Free Software Foundation, Inc.,
23# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24
25import os, sys
26import warnings
27import pickle
28import logging
29import atexit
30import traceback
31import ast
32import threading
33
34import bb.utils
35import bb.compat
36import bb.exceptions
37
38# This is the pid for which we should generate the event. This is set when
39# the runqueue forks off.
40worker_pid = 0
41worker_fire = None
42
43logger = logging.getLogger('BitBake.Event')
44
45class Event(object):
46    """Base class for events"""
47
48    def __init__(self):
49        self.pid = worker_pid
50
51
52class HeartbeatEvent(Event):
53    """Triggered at regular time intervals of 10 seconds. Other events can fire much more often
54       (runQueueTaskStarted when there are many short tasks) or not at all for long periods
55       of time (again runQueueTaskStarted, when there is just one long-running task), so this
56       event is more suitable for doing some task-independent work occassionally."""
57    def __init__(self, time):
58        Event.__init__(self)
59        self.time = time
60
61Registered        = 10
62AlreadyRegistered = 14
63
64def get_class_handlers():
65    return _handlers
66
67def set_class_handlers(h):
68    global _handlers
69    _handlers = h
70
71def clean_class_handlers():
72    return bb.compat.OrderedDict()
73
74# Internal
75_handlers = clean_class_handlers()
76_ui_handlers = {}
77_ui_logfilters = {}
78_ui_handler_seq = 0
79_event_handler_map = {}
80_catchall_handlers = {}
81_eventfilter = None
82_uiready = False
83_thread_lock = threading.Lock()
84_thread_lock_enabled = False
85
86if hasattr(__builtins__, '__setitem__'):
87    builtins = __builtins__
88else:
89    builtins = __builtins__.__dict__
90
91def enable_threadlock():
92    global _thread_lock_enabled
93    _thread_lock_enabled = True
94
95def disable_threadlock():
96    global _thread_lock_enabled
97    _thread_lock_enabled = False
98
99def execute_handler(name, handler, event, d):
100    event.data = d
101    addedd = False
102    if 'd' not in builtins:
103        builtins['d'] = d
104        addedd = True
105    try:
106        ret = handler(event)
107    except (bb.parse.SkipRecipe, bb.BBHandledException):
108        raise
109    except Exception:
110        etype, value, tb = sys.exc_info()
111        logger.error("Execution of event handler '%s' failed" % name,
112                        exc_info=(etype, value, tb.tb_next))
113        raise
114    except SystemExit as exc:
115        if exc.code != 0:
116            logger.error("Execution of event handler '%s' failed" % name)
117        raise
118    finally:
119        del event.data
120        if addedd:
121            del builtins['d']
122
123def fire_class_handlers(event, d):
124    if isinstance(event, logging.LogRecord):
125        return
126
127    eid = str(event.__class__)[8:-2]
128    evt_hmap = _event_handler_map.get(eid, {})
129    for name, handler in list(_handlers.items()):
130        if name in _catchall_handlers or name in evt_hmap:
131            if _eventfilter:
132                if not _eventfilter(name, handler, event, d):
133                    continue
134            execute_handler(name, handler, event, d)
135
136ui_queue = []
137@atexit.register
138def print_ui_queue():
139    """If we're exiting before a UI has been spawned, display any queued
140    LogRecords to the console."""
141    logger = logging.getLogger("BitBake")
142    if not _uiready:
143        from bb.msg import BBLogFormatter
144        # Flush any existing buffered content
145        sys.stdout.flush()
146        sys.stderr.flush()
147        stdout = logging.StreamHandler(sys.stdout)
148        stderr = logging.StreamHandler(sys.stderr)
149        formatter = BBLogFormatter("%(levelname)s: %(message)s")
150        stdout.setFormatter(formatter)
151        stderr.setFormatter(formatter)
152
153        # First check to see if we have any proper messages
154        msgprint = False
155        msgerrs = False
156
157        # Should we print to stderr?
158        for event in ui_queue[:]:
159            if isinstance(event, logging.LogRecord) and event.levelno >= logging.WARNING:
160                msgerrs = True
161                break
162
163        if msgerrs:
164            logger.addHandler(stderr)
165        else:
166            logger.addHandler(stdout)
167
168        for event in ui_queue[:]:
169            if isinstance(event, logging.LogRecord):
170                if event.levelno > logging.DEBUG:
171                    logger.handle(event)
172                    msgprint = True
173
174        # Nope, so just print all of the messages we have (including debug messages)
175        if not msgprint:
176            for event in ui_queue[:]:
177                if isinstance(event, logging.LogRecord):
178                    logger.handle(event)
179        if msgerrs:
180            logger.removeHandler(stderr)
181        else:
182            logger.removeHandler(stdout)
183
184def fire_ui_handlers(event, d):
185    global _thread_lock
186    global _thread_lock_enabled
187
188    if not _uiready:
189        # No UI handlers registered yet, queue up the messages
190        ui_queue.append(event)
191        return
192
193    if _thread_lock_enabled:
194        _thread_lock.acquire()
195
196    errors = []
197    for h in _ui_handlers:
198        #print "Sending event %s" % event
199        try:
200             if not _ui_logfilters[h].filter(event):
201                 continue
202             # We use pickle here since it better handles object instances
203             # which xmlrpc's marshaller does not. Events *must* be serializable
204             # by pickle.
205             if hasattr(_ui_handlers[h].event, "sendpickle"):
206                _ui_handlers[h].event.sendpickle((pickle.dumps(event)))
207             else:
208                _ui_handlers[h].event.send(event)
209        except:
210            errors.append(h)
211    for h in errors:
212        del _ui_handlers[h]
213
214    if _thread_lock_enabled:
215        _thread_lock.release()
216
217def fire(event, d):
218    """Fire off an Event"""
219
220    # We can fire class handlers in the worker process context and this is
221    # desired so they get the task based datastore.
222    # UI handlers need to be fired in the server context so we defer this. They
223    # don't have a datastore so the datastore context isn't a problem.
224
225    fire_class_handlers(event, d)
226    if worker_fire:
227        worker_fire(event, d)
228    else:
229        # If messages have been queued up, clear the queue
230        global _uiready, ui_queue
231        if _uiready and ui_queue:
232            for queue_event in ui_queue:
233                fire_ui_handlers(queue_event, d)
234            ui_queue = []
235        fire_ui_handlers(event, d)
236
237def fire_from_worker(event, d):
238    fire_ui_handlers(event, d)
239
240noop = lambda _: None
241def register(name, handler, mask=None, filename=None, lineno=None):
242    """Register an Event handler"""
243
244    # already registered
245    if name in _handlers:
246        return AlreadyRegistered
247
248    if handler is not None:
249        # handle string containing python code
250        if isinstance(handler, str):
251            tmp = "def %s(e):\n%s" % (name, handler)
252            try:
253                code = bb.methodpool.compile_cache(tmp)
254                if not code:
255                    if filename is None:
256                        filename = "%s(e)" % name
257                    code = compile(tmp, filename, "exec", ast.PyCF_ONLY_AST)
258                    if lineno is not None:
259                        ast.increment_lineno(code, lineno-1)
260                    code = compile(code, filename, "exec")
261                    bb.methodpool.compile_cache_add(tmp, code)
262            except SyntaxError:
263                logger.error("Unable to register event handler '%s':\n%s", name,
264                             ''.join(traceback.format_exc(limit=0)))
265                _handlers[name] = noop
266                return
267            env = {}
268            bb.utils.better_exec(code, env)
269            func = bb.utils.better_eval(name, env)
270            _handlers[name] = func
271        else:
272            _handlers[name] = handler
273
274        if not mask or '*' in mask:
275            _catchall_handlers[name] = True
276        else:
277            for m in mask:
278                if _event_handler_map.get(m, None) is None:
279                    _event_handler_map[m] = {}
280                _event_handler_map[m][name] = True
281
282        return Registered
283
284def remove(name, handler):
285    """Remove an Event handler"""
286    _handlers.pop(name)
287    if name in _catchall_handlers:
288        _catchall_handlers.pop(name)
289    for event in _event_handler_map.keys():
290        if name in _event_handler_map[event]:
291            _event_handler_map[event].pop(name)
292
293def get_handlers():
294    return _handlers
295
296def set_handlers(handlers):
297    global _handlers
298    _handlers = handlers
299
300def set_eventfilter(func):
301    global _eventfilter
302    _eventfilter = func
303
304def register_UIHhandler(handler, mainui=False):
305    bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1
306    _ui_handlers[_ui_handler_seq] = handler
307    level, debug_domains = bb.msg.constructLogOptions()
308    _ui_logfilters[_ui_handler_seq] = UIEventFilter(level, debug_domains)
309    if mainui:
310        global _uiready
311        _uiready = _ui_handler_seq
312    return _ui_handler_seq
313
314def unregister_UIHhandler(handlerNum, mainui=False):
315    if mainui:
316        global _uiready
317        _uiready = False
318    if handlerNum in _ui_handlers:
319        del _ui_handlers[handlerNum]
320    return
321
322def get_uihandler():
323    if _uiready is False:
324        return None
325    return _uiready
326
327# Class to allow filtering of events and specific filtering of LogRecords *before* we put them over the IPC
328class UIEventFilter(object):
329    def __init__(self, level, debug_domains):
330        self.update(None, level, debug_domains)
331
332    def update(self, eventmask, level, debug_domains):
333        self.eventmask = eventmask
334        self.stdlevel = level
335        self.debug_domains = debug_domains
336
337    def filter(self, event):
338        if isinstance(event, logging.LogRecord):
339            if event.levelno >= self.stdlevel:
340                return True
341            if event.name in self.debug_domains and event.levelno >= self.debug_domains[event.name]:
342                return True
343            return False
344        eid = str(event.__class__)[8:-2]
345        if self.eventmask and eid not in self.eventmask:
346            return False
347        return True
348
349def set_UIHmask(handlerNum, level, debug_domains, mask):
350    if not handlerNum in _ui_handlers:
351        return False
352    if '*' in mask:
353        _ui_logfilters[handlerNum].update(None, level, debug_domains)
354    else:
355        _ui_logfilters[handlerNum].update(mask, level, debug_domains)
356    return True
357
358def getName(e):
359    """Returns the name of a class or class instance"""
360    if getattr(e, "__name__", None) == None:
361        return e.__class__.__name__
362    else:
363        return e.__name__
364
365class OperationStarted(Event):
366    """An operation has begun"""
367    def __init__(self, msg = "Operation Started"):
368        Event.__init__(self)
369        self.msg = msg
370
371class OperationCompleted(Event):
372    """An operation has completed"""
373    def __init__(self, total, msg = "Operation Completed"):
374        Event.__init__(self)
375        self.total = total
376        self.msg = msg
377
378class OperationProgress(Event):
379    """An operation is in progress"""
380    def __init__(self, current, total, msg = "Operation in Progress"):
381        Event.__init__(self)
382        self.current = current
383        self.total = total
384        self.msg = msg + ": %s/%s" % (current, total);
385
386class ConfigParsed(Event):
387    """Configuration Parsing Complete"""
388
389class MultiConfigParsed(Event):
390    """Multi-Config Parsing Complete"""
391    def __init__(self, mcdata):
392        self.mcdata = mcdata
393        Event.__init__(self)
394
395class RecipeEvent(Event):
396    def __init__(self, fn):
397        self.fn = fn
398        Event.__init__(self)
399
400class RecipePreFinalise(RecipeEvent):
401    """ Recipe Parsing Complete but not yet finalised"""
402
403class RecipeTaskPreProcess(RecipeEvent):
404    """
405    Recipe Tasks about to be finalised
406    The list of tasks should be final at this point and handlers
407    are only able to change interdependencies
408    """
409    def __init__(self, fn, tasklist):
410        self.fn = fn
411        self.tasklist = tasklist
412        Event.__init__(self)
413
414class RecipeParsed(RecipeEvent):
415    """ Recipe Parsing Complete """
416
417class StampUpdate(Event):
418    """Trigger for any adjustment of the stamp files to happen"""
419
420    def __init__(self, targets, stampfns):
421        self._targets = targets
422        self._stampfns = stampfns
423        Event.__init__(self)
424
425    def getStampPrefix(self):
426        return self._stampfns
427
428    def getTargets(self):
429        return self._targets
430
431    stampPrefix = property(getStampPrefix)
432    targets = property(getTargets)
433
434class BuildBase(Event):
435    """Base class for bitbake build events"""
436
437    def __init__(self, n, p, failures = 0):
438        self._name = n
439        self._pkgs = p
440        Event.__init__(self)
441        self._failures = failures
442
443    def getPkgs(self):
444        return self._pkgs
445
446    def setPkgs(self, pkgs):
447        self._pkgs = pkgs
448
449    def getName(self):
450        return self._name
451
452    def setName(self, name):
453        self._name = name
454
455    def getFailures(self):
456        """
457        Return the number of failed packages
458        """
459        return self._failures
460
461    pkgs = property(getPkgs, setPkgs, None, "pkgs property")
462    name = property(getName, setName, None, "name property")
463
464class BuildInit(BuildBase):
465    """buildFile or buildTargets was invoked"""
466    def __init__(self, p=[]):
467        name = None
468        BuildBase.__init__(self, name, p)
469
470class BuildStarted(BuildBase, OperationStarted):
471    """Event when builds start"""
472    def __init__(self, n, p, failures = 0):
473        OperationStarted.__init__(self, "Building Started")
474        BuildBase.__init__(self, n, p, failures)
475
476class BuildCompleted(BuildBase, OperationCompleted):
477    """Event when builds have completed"""
478    def __init__(self, total, n, p, failures=0, interrupted=0):
479        if not failures:
480            OperationCompleted.__init__(self, total, "Building Succeeded")
481        else:
482            OperationCompleted.__init__(self, total, "Building Failed")
483        self._interrupted = interrupted
484        BuildBase.__init__(self, n, p, failures)
485
486class DiskFull(Event):
487    """Disk full case build aborted"""
488    def __init__(self, dev, type, freespace, mountpoint):
489        Event.__init__(self)
490        self._dev = dev
491        self._type = type
492        self._free = freespace
493        self._mountpoint = mountpoint
494
495class DiskUsageSample:
496    def __init__(self, available_bytes, free_bytes, total_bytes):
497        # Number of bytes available to non-root processes.
498        self.available_bytes = available_bytes
499        # Number of bytes available to root processes.
500        self.free_bytes = free_bytes
501        # Total capacity of the volume.
502        self.total_bytes = total_bytes
503
504class MonitorDiskEvent(Event):
505    """If BB_DISKMON_DIRS is set, then this event gets triggered each time disk space is checked.
506       Provides information about devices that are getting monitored."""
507    def __init__(self, disk_usage):
508        Event.__init__(self)
509        # hash of device root path -> DiskUsageSample
510        self.disk_usage = disk_usage
511
512class NoProvider(Event):
513    """No Provider for an Event"""
514
515    def __init__(self, item, runtime=False, dependees=None, reasons=None, close_matches=None):
516        Event.__init__(self)
517        self._item = item
518        self._runtime = runtime
519        self._dependees = dependees
520        self._reasons = reasons
521        self._close_matches = close_matches
522
523    def getItem(self):
524        return self._item
525
526    def isRuntime(self):
527        return self._runtime
528
529    def __str__(self):
530        msg = ''
531        if self._runtime:
532            r = "R"
533        else:
534            r = ""
535
536        extra = ''
537        if not self._reasons:
538            if self._close_matches:
539                extra = ". Close matches:\n  %s" % '\n  '.join(self._close_matches)
540
541        if self._dependees:
542            msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s" % (r, self._item, ", ".join(self._dependees), r, extra)
543        else:
544            msg = "Nothing %sPROVIDES '%s'%s" % (r, self._item, extra)
545        if self._reasons:
546            for reason in self._reasons:
547                msg += '\n' + reason
548        return msg
549
550
551class MultipleProviders(Event):
552    """Multiple Providers"""
553
554    def  __init__(self, item, candidates, runtime = False):
555        Event.__init__(self)
556        self._item = item
557        self._candidates = candidates
558        self._is_runtime = runtime
559
560    def isRuntime(self):
561        """
562        Is this a runtime issue?
563        """
564        return self._is_runtime
565
566    def getItem(self):
567        """
568        The name for the to be build item
569        """
570        return self._item
571
572    def getCandidates(self):
573        """
574        Get the possible Candidates for a PROVIDER.
575        """
576        return self._candidates
577
578    def __str__(self):
579        msg = "Multiple providers are available for %s%s (%s)" % (self._is_runtime and "runtime " or "",
580                            self._item,
581                            ", ".join(self._candidates))
582        rtime = ""
583        if self._is_runtime:
584            rtime = "R"
585        msg += "\nConsider defining a PREFERRED_%sPROVIDER entry to match %s" % (rtime, self._item)
586        return msg
587
588class ParseStarted(OperationStarted):
589    """Recipe parsing for the runqueue has begun"""
590    def __init__(self, total):
591        OperationStarted.__init__(self, "Recipe parsing Started")
592        self.total = total
593
594class ParseCompleted(OperationCompleted):
595    """Recipe parsing for the runqueue has completed"""
596    def __init__(self, cached, parsed, skipped, masked, virtuals, errors, total):
597        OperationCompleted.__init__(self, total, "Recipe parsing Completed")
598        self.cached = cached
599        self.parsed = parsed
600        self.skipped = skipped
601        self.virtuals = virtuals
602        self.masked = masked
603        self.errors = errors
604        self.sofar = cached + parsed
605
606class ParseProgress(OperationProgress):
607    """Recipe parsing progress"""
608    def __init__(self, current, total):
609        OperationProgress.__init__(self, current, total, "Recipe parsing")
610
611
612class CacheLoadStarted(OperationStarted):
613    """Loading of the dependency cache has begun"""
614    def __init__(self, total):
615        OperationStarted.__init__(self, "Loading cache Started")
616        self.total = total
617
618class CacheLoadProgress(OperationProgress):
619    """Cache loading progress"""
620    def __init__(self, current, total):
621        OperationProgress.__init__(self, current, total, "Loading cache")
622
623class CacheLoadCompleted(OperationCompleted):
624    """Cache loading is complete"""
625    def __init__(self, total, num_entries):
626        OperationCompleted.__init__(self, total, "Loading cache Completed")
627        self.num_entries = num_entries
628
629class TreeDataPreparationStarted(OperationStarted):
630    """Tree data preparation started"""
631    def __init__(self):
632        OperationStarted.__init__(self, "Preparing tree data Started")
633
634class TreeDataPreparationProgress(OperationProgress):
635    """Tree data preparation is in progress"""
636    def __init__(self, current, total):
637        OperationProgress.__init__(self, current, total, "Preparing tree data")
638
639class TreeDataPreparationCompleted(OperationCompleted):
640    """Tree data preparation completed"""
641    def __init__(self, total):
642        OperationCompleted.__init__(self, total, "Preparing tree data Completed")
643
644class DepTreeGenerated(Event):
645    """
646    Event when a dependency tree has been generated
647    """
648
649    def __init__(self, depgraph):
650        Event.__init__(self)
651        self._depgraph = depgraph
652
653class TargetsTreeGenerated(Event):
654    """
655    Event when a set of buildable targets has been generated
656    """
657    def __init__(self, model):
658        Event.__init__(self)
659        self._model = model
660
661class ReachableStamps(Event):
662    """
663    An event listing all stamps reachable after parsing
664    which the metadata may use to clean up stale data
665    """
666
667    def __init__(self, stamps):
668        Event.__init__(self)
669        self.stamps = stamps
670
671class FilesMatchingFound(Event):
672    """
673    Event when a list of files matching the supplied pattern has
674    been generated
675    """
676    def __init__(self, pattern, matches):
677        Event.__init__(self)
678        self._pattern = pattern
679        self._matches = matches
680
681class ConfigFilesFound(Event):
682    """
683    Event when a list of appropriate config files has been generated
684    """
685    def __init__(self, variable, values):
686        Event.__init__(self)
687        self._variable = variable
688        self._values = values
689
690class ConfigFilePathFound(Event):
691    """
692    Event when a path for a config file has been found
693    """
694    def __init__(self, path):
695        Event.__init__(self)
696        self._path = path
697
698class MsgBase(Event):
699    """Base class for messages"""
700
701    def __init__(self, msg):
702        self._message = msg
703        Event.__init__(self)
704
705class MsgDebug(MsgBase):
706    """Debug Message"""
707
708class MsgNote(MsgBase):
709    """Note Message"""
710
711class MsgWarn(MsgBase):
712    """Warning Message"""
713
714class MsgError(MsgBase):
715    """Error Message"""
716
717class MsgFatal(MsgBase):
718    """Fatal Message"""
719
720class MsgPlain(MsgBase):
721    """General output"""
722
723class LogExecTTY(Event):
724    """Send event containing program to spawn on tty of the logger"""
725    def __init__(self, msg, prog, sleep_delay, retries):
726        Event.__init__(self)
727        self.msg = msg
728        self.prog = prog
729        self.sleep_delay = sleep_delay
730        self.retries = retries
731
732class LogHandler(logging.Handler):
733    """Dispatch logging messages as bitbake events"""
734
735    def emit(self, record):
736        if record.exc_info:
737            etype, value, tb = record.exc_info
738            if hasattr(tb, 'tb_next'):
739                tb = list(bb.exceptions.extract_traceback(tb, context=3))
740            # Need to turn the value into something the logging system can pickle
741            record.bb_exc_info = (etype, value, tb)
742            record.bb_exc_formatted = bb.exceptions.format_exception(etype, value, tb, limit=5)
743            value = str(value)
744            record.exc_info = None
745        fire(record, None)
746
747    def filter(self, record):
748        record.taskpid = worker_pid
749        return True
750
751class MetadataEvent(Event):
752    """
753    Generic event that target for OE-Core classes
754    to report information during asynchrous execution
755    """
756    def __init__(self, eventtype, eventdata):
757        Event.__init__(self)
758        self.type = eventtype
759        self._localdata = eventdata
760
761class ProcessStarted(Event):
762    """
763    Generic process started event (usually part of the initial startup)
764    where further progress events will be delivered
765    """
766    def __init__(self, processname, total):
767        Event.__init__(self)
768        self.processname = processname
769        self.total = total
770
771class ProcessProgress(Event):
772    """
773    Generic process progress event (usually part of the initial startup)
774    """
775    def __init__(self, processname, progress):
776        Event.__init__(self)
777        self.processname = processname
778        self.progress = progress
779
780class ProcessFinished(Event):
781    """
782    Generic process finished event (usually part of the initial startup)
783    """
784    def __init__(self, processname):
785        Event.__init__(self)
786        self.processname = processname
787
788class SanityCheck(Event):
789    """
790    Event to run sanity checks, either raise errors or generate events as return status.
791    """
792    def __init__(self, generateevents = True):
793        Event.__init__(self)
794        self.generateevents = generateevents
795
796class SanityCheckPassed(Event):
797    """
798    Event to indicate sanity check has passed
799    """
800
801class SanityCheckFailed(Event):
802    """
803    Event to indicate sanity check has failed
804    """
805    def __init__(self, msg, network_error=False):
806        Event.__init__(self)
807        self._msg = msg
808        self._network_error = network_error
809
810class NetworkTest(Event):
811    """
812    Event to run network connectivity tests, either raise errors or generate events as return status.
813    """
814    def __init__(self, generateevents = True):
815        Event.__init__(self)
816        self.generateevents = generateevents
817
818class NetworkTestPassed(Event):
819    """
820    Event to indicate network test has passed
821    """
822
823class NetworkTestFailed(Event):
824    """
825    Event to indicate network test has failed
826    """
827
828class FindSigInfoResult(Event):
829    """
830    Event to return results from findSigInfo command
831    """
832    def __init__(self, result):
833        Event.__init__(self)
834        self.result = result
835