18903: add a date range option to the user activity report.
[arvados.git] / tools / user-activity / arvados_user_activity / main.py
1 #!/usr/bin/env python3
2 # Copyright (C) The Arvados Authors. All rights reserved.
3 #
4 # SPDX-License-Identifier: AGPL-3.0
5
6 import argparse
7 import sys
8
9 import arvados
10 import arvados.util
11 import datetime
12 import ciso8601
13
14 def parse_arguments(arguments):
15     arg_parser = argparse.ArgumentParser()
16     arg_parser.add_argument('--start', help='Start date for the report in YYYY-MM-DD format')
17     arg_parser.add_argument('--end', help='End date for the report in YYYY-MM-DD format')
18     arg_parser.add_argument('--days', type=int, help='Number of days before now() to start the report')
19     args = arg_parser.parse_args(arguments)
20
21     if args.days and (args.start or args.end):
22         p.print_help()
23         print("Error: either specify --days or both --start and --end")
24         exit(1)
25
26     if not args.days and (not args.start or not args.end):
27         p.print_help()
28         print("Error: either specify --days or both --start and --end")
29         exit(1)
30
31     if (args.start and not args.end) or (args.end and not args.start):
32         p.print_help()
33         print("Error: no start or end date found, either specify --days or both --start and --end")
34         exit(1)
35
36     return args
37
38 def getowner(arv, uuid, owners):
39     if uuid is None:
40         return None
41     if uuid[6:11] == "tpzed":
42         return uuid
43
44     if uuid not in owners:
45         try:
46             gp = arv.groups().get(uuid=uuid).execute()
47             owners[uuid] = gp["owner_uuid"]
48         except:
49             owners[uuid] = None
50
51     return getowner(arv, owners[uuid], owners)
52
53 def getuserinfo(arv, uuid):
54     try:
55         u = arv.users().get(uuid=uuid).execute()
56     except:
57         return "deleted user (%susers/%s)" % (arv.config()["Services"]["Workbench1"]["ExternalURL"],
58                                                        uuid)
59     prof = "\n".join("  %s: \"%s\"" % (k, v) for k, v in u["prefs"].get("profile", {}).items() if v)
60     if prof:
61         prof = "\n"+prof+"\n"
62     return "%s %s <%s> (%susers/%s)%s" % (u["first_name"], u["last_name"], u["email"],
63                                                        arv.config()["Services"]["Workbench1"]["ExternalURL"],
64                                                        uuid, prof)
65
66 collectionNameCache = {}
67 def getCollectionName(arv, pdh):
68     if pdh not in collectionNameCache:
69         u = arv.collections().list(filters=[["portable_data_hash","=",pdh]]).execute().get("items")
70         if len(u) < 1:
71             return "(deleted)"
72         collectionNameCache[pdh] = u[0]["name"]
73     return collectionNameCache[pdh]
74
75 def getname(u):
76     return "\"%s\" (%s)" % (u["name"], u["uuid"])
77
78 def main(arguments=None):
79     if arguments is None:
80         arguments = sys.argv[1:]
81
82     args = parse_arguments(arguments)
83
84     arv = arvados.api()
85
86     if args.days:
87         to = datetime.datetime.utcnow()
88         since = to - datetime.timedelta(days=args.days)
89
90     if args.start:
91         try:
92             since = datetime.datetime.strptime(args.start,"%Y-%m-%d")
93         except:
94             p.print_help()
95             print("Error: start date must be in YYYY-MM-DD format")
96             exit(1)
97
98     if args.end:
99         try:
100             to = datetime.datetime.strptime(args.end,"%Y-%m-%d")
101         except:
102             p.print_help()
103             print("Error: end date must be in YYYY-MM-DD format")
104             exit(1)
105
106     print("User activity on %s between %s and %s\n" % (arv.config()["ClusterID"],
107                                                        since.isoformat(sep=" ", timespec="minutes"),
108                                                        to.isoformat(sep=" ", timespec="minutes")))
109
110     events = arvados.util.keyset_list_all(arv.logs().list, filters=[["created_at", ">=", since.isoformat()],["created_at", "<", to.isoformat()]])
111
112     users = {}
113     owners = {}
114
115     for e in events:
116         owner = getowner(arv, e["object_owner_uuid"], owners)
117         users.setdefault(owner, [])
118         event_at = ciso8601.parse_datetime(e["event_at"]).astimezone().isoformat(sep=" ", timespec="minutes")
119         # loguuid = e["uuid"]
120         loguuid = ""
121
122         if e["event_type"] == "create" and e["object_uuid"][6:11] == "tpzed":
123             users.setdefault(e["object_uuid"], [])
124             users[e["object_uuid"]].append("%s User account created" % event_at)
125
126         elif e["event_type"] == "update" and e["object_uuid"][6:11] == "tpzed":
127             pass
128
129         elif e["event_type"] == "create" and e["object_uuid"][6:11] == "xvhdp":
130             if e["properties"]["new_attributes"]["requesting_container_uuid"] is None:
131                 users[owner].append("%s Ran container %s %s" % (event_at, getname(e["properties"]["new_attributes"]), loguuid))
132
133         elif e["event_type"] == "update" and e["object_uuid"][6:11] == "xvhdp":
134             pass
135
136         elif e["event_type"] == "create" and e["object_uuid"][6:11] == "j7d0g":
137             users[owner].append("%s Created project %s" %  (event_at, getname(e["properties"]["new_attributes"])))
138
139         elif e["event_type"] == "delete" and e["object_uuid"][6:11] == "j7d0g":
140             users[owner].append("%s Deleted project %s" % (event_at, getname(e["properties"]["old_attributes"])))
141
142         elif e["event_type"] == "update" and e["object_uuid"][6:11] == "j7d0g":
143             users[owner].append("%s Updated project %s" % (event_at, getname(e["properties"]["new_attributes"])))
144
145         elif e["event_type"] in ("create", "update") and e["object_uuid"][6:11] == "gj3su":
146             since_last = None
147             if len(users[owner]) > 0 and users[owner][-1].endswith("activity"):
148                 sp = users[owner][-1].split(" ")
149                 start = sp[0]+" "+sp[1]
150                 since_last = ciso8601.parse_datetime(event_at) - ciso8601.parse_datetime(sp[3]+" "+sp[4])
151                 span = ciso8601.parse_datetime(event_at) - ciso8601.parse_datetime(start)
152
153             if since_last is not None and since_last < datetime.timedelta(minutes=61):
154                 users[owner][-1] = "%s to %s (%02d:%02d) Account activity" % (start, event_at, span.days*24 + int(span.seconds/3600), int((span.seconds % 3600)/60))
155             else:
156                 users[owner].append("%s to %s (0:00) Account activity" % (event_at, event_at))
157
158         elif e["event_type"] == "create" and e["object_uuid"][6:11] == "o0j2j":
159             if e["properties"]["new_attributes"]["link_class"] == "tag":
160                 users[owner].append("%s Tagged %s" % (event_at, e["properties"]["new_attributes"]["head_uuid"]))
161             elif e["properties"]["new_attributes"]["link_class"] == "permission":
162                 users[owner].append("%s Shared %s with %s" % (event_at, e["properties"]["new_attributes"]["tail_uuid"], e["properties"]["new_attributes"]["head_uuid"]))
163             else:
164                 users[owner].append("%s %s %s %s %s" % (event_at, e["event_type"], e["object_kind"], e["object_uuid"], loguuid))
165
166         elif e["event_type"] == "delete" and e["object_uuid"][6:11] == "o0j2j":
167             if e["properties"]["old_attributes"]["link_class"] == "tag":
168                 users[owner].append("%s Untagged %s" % (event_at, e["properties"]["old_attributes"]["head_uuid"]))
169             elif e["properties"]["old_attributes"]["link_class"] == "permission":
170                 users[owner].append("%s Unshared %s with %s" % (event_at, e["properties"]["old_attributes"]["tail_uuid"], e["properties"]["old_attributes"]["head_uuid"]))
171             else:
172                 users[owner].append("%s %s %s %s %s" % (event_at, e["event_type"], e["object_kind"], e["object_uuid"], loguuid))
173
174         elif e["event_type"] == "create" and e["object_uuid"][6:11] == "4zz18":
175             if e["properties"]["new_attributes"]["properties"].get("type") in ("log", "output", "intermediate"):
176                 pass
177             else:
178                 users[owner].append("%s Created collection %s %s" % (event_at, getname(e["properties"]["new_attributes"]), loguuid))
179
180         elif e["event_type"] == "update" and e["object_uuid"][6:11] == "4zz18":
181             users[owner].append("%s Updated collection %s %s" % (event_at, getname(e["properties"]["new_attributes"]), loguuid))
182
183         elif e["event_type"] == "delete" and e["object_uuid"][6:11] == "4zz18":
184             if e["properties"]["old_attributes"]["properties"].get("type") in ("log", "output", "intermediate"):
185                 pass
186             else:
187                 users[owner].append("%s Deleted collection %s %s" % (event_at, getname(e["properties"]["old_attributes"]), loguuid))
188
189         elif e["event_type"] == "file_download":
190                 users.setdefault(e["object_uuid"], [])
191                 users[e["object_uuid"]].append("%s Downloaded file \"%s\" from \"%s\" (%s) (%s)" % (event_at,
192                                                                                        e["properties"].get("collection_file_path") or e["properties"].get("reqPath"),
193                                                                                        getCollectionName(arv, e["properties"].get("portable_data_hash")),
194                                                                                        e["properties"].get("collection_uuid"),
195                                                                                        e["properties"].get("portable_data_hash")))
196
197         elif e["event_type"] == "file_upload":
198                 users.setdefault(e["object_uuid"], [])
199                 users[e["object_uuid"]].append("%s Uploaded file \"%s\" to \"%s\" (%s)" % (event_at,
200                                                                                     e["properties"].get("collection_file_path") or e["properties"].get("reqPath"),
201                                                                                     getCollectionName(arv, e["properties"].get("portable_data_hash")),
202                                                                                     e["properties"].get("collection_uuid")))
203
204         else:
205             users[owner].append("%s %s %s %s %s" % (event_at, e["event_type"], e["object_kind"], e["object_uuid"], loguuid))
206
207     for k,v in users.items():
208         if k is None or k.endswith("-tpzed-000000000000000"):
209             continue
210         print(getuserinfo(arv, k))
211         for ev in v:
212             print("  %s" % ev)
213         print("")
214
215 if __name__ == "__main__":
216     main()