blob: 386daf7cb185b8bf3f10ea327bbb59f9926c6bf1 [file] [log] [blame]
Zhuoyao Zhangbae5f722024-09-20 16:53:59 +00001# Copyright 2024, The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15
16import getpass
17import logging
18import multiprocessing.connection
19import os
20import platform
21import time
22
23from atest.metrics import clearcut_client
24from atest.proto import clientanalytics_pb2
25from proto import edit_event_pb2
26from watchdog.events import FileSystemEvent
27from watchdog.events import PatternMatchingEventHandler
28from watchdog.observers import Observer
29
30# Enum of the Clearcut log source defined under
31# /google3/wireless/android/play/playlog/proto/log_source_enum.proto
32LOG_SOURCE = 2524
33
34
35class ClearcutEventHandler(PatternMatchingEventHandler):
36
37 def __init__(
38 self, path: str, cclient: clearcut_client.Clearcut | None = None
39 ):
40
41 super().__init__(patterns=["*"], ignore_directories=True)
42 self.root_monitoring_path = path
43 self.cclient = cclient or clearcut_client.Clearcut(LOG_SOURCE)
44
45 self.user_name = getpass.getuser()
46 self.host_name = platform.node()
47 self.source_root = os.environ.get("ANDROID_BUILD_TOP", "")
48
49 def on_moved(self, event: FileSystemEvent):
50 self._log_edit_event(event, edit_event_pb2.EditEvent.MOVE)
51
52 def on_created(self, event: FileSystemEvent):
53 self._log_edit_event(event, edit_event_pb2.EditEvent.CREATE)
54
55 def on_deleted(self, event: FileSystemEvent):
56 self._log_edit_event(event, edit_event_pb2.EditEvent.DELETE)
57
58 def on_modified(self, event: FileSystemEvent):
59 self._log_edit_event(event, edit_event_pb2.EditEvent.MODIFY)
60
61 def flushall(self):
62 logging.info("flushing all pending events.")
63 self.cclient.flush_events()
64
65 def _log_edit_event(
66 self, event: FileSystemEvent, edit_type: edit_event_pb2.EditEvent.EditType
67 ):
68 event_time = time.time()
69
70 logging.info("%s: %s", event.event_type, event.src_path)
71 try:
72 event_proto = edit_event_pb2.EditEvent(
73 user_name=self.user_name,
74 host_name=self.host_name,
75 source_root=self.source_root,
76 )
77 event_proto.single_edit_event.CopyFrom(
78 edit_event_pb2.EditEvent.SingleEditEvent(
79 file_path=event.src_path, edit_type=edit_type
80 )
81 )
82 clearcut_log_event = clientanalytics_pb2.LogEvent(
83 event_time_ms=int(event_time * 1000),
84 source_extension=event_proto.SerializeToString(),
85 )
86
87 self.cclient.log(clearcut_log_event)
88 except Exception:
89 logging.exception("Failed to log edit event.")
90
91
92def start(
93 path: str,
94 cclient: clearcut_client.Clearcut | None = None,
95 pipe_sender: multiprocessing.connection.Connection | None = None,
96):
97 """Method to start the edit monitor.
98
99 This is the entry point to start the edit monitor as a subprocess of
100 the daemon manager.
101
102 params:
103 path: The root path to monitor
104 cclient: The clearcut client to send the edit logs.
105 conn: the sender of the pipe to communicate with the deamon manager.
106 """
107 event_handler = ClearcutEventHandler(path, cclient)
108 observer = Observer()
109
110 logging.info("Starting observer on path %s.", path)
111 observer.schedule(event_handler, path, recursive=True)
112 observer.start()
113 logging.info("Observer started.")
114 if pipe_sender:
115 pipe_sender.send("Observer started.")
116
117 try:
118 while True:
119 time.sleep(1)
120 finally:
121 event_handler.flushall()
122 observer.stop()
123 observer.join()
124 if pipe_sender:
125 pipe_sender.close()