]> git.saurik.com Git - apple/security.git/blob - keychain/analytics/CKKSLaunchSequence.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / analytics / CKKSLaunchSequence.m
1 //
2 // CKKSLaunchSequence.m
3 //
4
5 #import "keychain/analytics/CKKSLaunchSequence.h"
6 #import <utilities/SecCoreAnalytics.h>
7 #import <os/assumes.h>
8
9 enum { CKKSMaxLaunchEvents = 100 };
10
11 @interface CKKSLaunchEvent : NSObject <NSCopying>
12 - (instancetype)init NS_UNAVAILABLE;
13 - (instancetype)initWithName:(NSString *)name;
14 @property (strong) NSString *name; // "human friendly" name, included in metrics
15 @property (strong) NSDate *date; // first, or last time this event happend (depends on usecase)
16 @property (assign) unsigned counter; //times this event happened, zero indexed
17 @end
18
19
20 @interface CKKSLaunchSequence () {
21 bool _firstLaunch;
22 }
23 @property (readwrite) bool launched;
24 @property (strong) NSString* name;
25 @property (strong) NSMutableDictionary<NSString *,CKKSLaunchEvent *>* events; // key is uniqifier, event.name is "human friendly"
26 @property (strong) NSMutableDictionary<NSString *,id>* attributes;
27
28 @property (strong) NSBlockOperation *launchOperation;
29 @property (strong) NSMutableDictionary<NSString *, CKKSLaunchSequence *> *dependantLaunches;
30 @end
31
32
33 @implementation CKKSLaunchEvent
34
35 - (instancetype)initWithName:(NSString *)name {
36 if ((self = [super init]) != NULL) {
37 _name = name;
38 _date = [NSDate date];
39 _counter = 1;
40 }
41 return self;
42 }
43
44 - (id)copyWithZone:(NSZone *)zone
45 {
46 CKKSLaunchEvent *copy = [[[self class] alloc] init];
47
48 copy.name = [self.name copyWithZone:zone];
49 copy.date = [self.date copyWithZone:zone];
50 copy.counter = self.counter;
51
52 return copy;
53 }
54
55 @end
56
57 @implementation CKKSLaunchSequence
58
59 - (instancetype)initWithRocketName:(NSString *)name {
60 if ((self = [super init]) != NULL) {
61 _name = name;
62 _events = [NSMutableDictionary dictionary];
63 _events[@"started"] = [[CKKSLaunchEvent alloc] initWithName:@"started"];
64 _launchOperation = [[NSBlockOperation alloc] init];
65 _dependantLaunches = [NSMutableDictionary dictionary];
66 }
67 return self;
68 }
69
70 - (bool)firstLaunch {
71 @synchronized(self) {
72 return _firstLaunch;
73 }
74 }
75
76 - (void)setFirstLaunch:(bool)firstLaunch
77 {
78 @synchronized (self) {
79 if (self.launched) {
80 return;
81 }
82 _firstLaunch = firstLaunch;
83 }
84 }
85
86 - (void)addDependantLaunch:(NSString *)name child:(CKKSLaunchSequence *)child
87 {
88 @synchronized (self) {
89 if (self.launched) {
90 return;
91 }
92 if (self.dependantLaunches[name]) {
93 return;
94 }
95 self.dependantLaunches[name] = child;
96 [self.launchOperation addDependency:child.launchOperation];
97 }
98 }
99
100
101 - (void)addAttribute:(NSString *)key value:(id)value {
102 if (![key isKindOfClass:[NSString class]]) {
103 return;
104 }
105 @synchronized(self) {
106 if (self.launched) {
107 return;
108 }
109 if (self.attributes == nil) {
110 self.attributes = [NSMutableDictionary dictionary];
111 }
112 self.attributes[key] = value;
113 }
114 }
115
116
117 - (void)addEvent:(NSString *)eventname {
118 if (![eventname isKindOfClass:[NSString class]]) {
119 return;
120 }
121 @synchronized(self) {
122 if (self.launched) {
123 return;
124 }
125 if (self.events.count > CKKSMaxLaunchEvents) {
126 return;
127 }
128 CKKSLaunchEvent *event = self.events[eventname];
129 if (event) {
130 event.counter++;
131 } else {
132 event = [[CKKSLaunchEvent alloc] initWithName:eventname];
133 }
134 self.events[eventname] = event;
135 }
136 }
137
138 - (void)reportMetric {
139
140 NSMutableDictionary *metric = [NSMutableDictionary dictionary];
141 os_assert(self.launched);
142
143 @synchronized(self) {
144 /* don't need to lock children, at this point, they have launched and will no longer mutate */
145 [self.dependantLaunches enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull childKey, CKKSLaunchSequence *_Nonnull child, BOOL * _Nonnull stop) {
146 os_assert(child.launched);
147 [child.events enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, CKKSLaunchEvent * _Nonnull obj, BOOL * _Nonnull stop) {
148 CKKSLaunchEvent *childEvent = [obj copy];
149 childEvent.name = [NSString stringWithFormat:@"c:%@-%@", childKey, childEvent.name];
150 self.events[[NSString stringWithFormat:@"c:%@-%@", childKey, key]] = childEvent;
151 }];
152 self.attributes[[NSString stringWithFormat:@"c:%@", childKey]] = child.attributes;
153 }];
154
155 CKKSLaunchEvent *event = [[CKKSLaunchEvent alloc] initWithName:(self.firstLaunch ? @"first-launch" : @"re-launch")];
156 self.events[event.name] = event;
157
158 metric[@"events"] = [self eventsRelativeTime];
159 if (self.attributes.count) {
160 metric[@"attributes"] = self.attributes;
161 }
162
163 }
164 [SecCoreAnalytics sendEvent:self.name event:metric];
165 }
166
167 - (void)launch {
168 @synchronized(self) {
169 if (self.launched) {
170 return;
171 }
172 self.launched = true;
173 }
174
175 __weak typeof(self) weakSelf = self;
176
177 [self.launchOperation addExecutionBlock:^{
178 [weakSelf reportMetric];
179 }];
180
181 [[NSOperationQueue mainQueue] addOperation:self.launchOperation];
182 }
183
184 - (NSArray *) eventsRelativeTime
185 {
186 NSMutableArray<CKKSLaunchEvent *>* array = [NSMutableArray array];
187 [self.events enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull __unused name, CKKSLaunchEvent * _Nonnull event, BOOL * _Nonnull __unused stop) {
188 [array addObject:event];
189 }];
190 [array sortUsingComparator:^NSComparisonResult(CKKSLaunchEvent * _Nonnull obj1, CKKSLaunchEvent *_Nonnull obj2) {
191 return [obj1.date compare:obj2.date];
192 }];
193 NSDate *firstEvent = array[0].date;
194 NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
195 [array enumerateObjectsUsingBlock:^(CKKSLaunchEvent * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
196 NSMutableDictionary *event = [NSMutableDictionary dictionary];
197 event[@"name"] = obj.name;
198 event[@"time"] = @([obj.date timeIntervalSinceDate:firstEvent]);
199 if (obj.counter) {
200 event[@"counter"] = @(obj.counter);
201 }
202 [result addObject:event];
203 }];
204 return result;
205 }
206
207 - (NSArray *) eventsByTime {
208 @synchronized (self) {
209 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
210 dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ";
211
212 NSMutableArray<NSString *>* array = [NSMutableArray array];
213 [self.events enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull name, CKKSLaunchEvent * _Nonnull event, BOOL * _Nonnull stop) {
214 NSString *str = [NSString stringWithFormat:@"%@ - %@:%u", [dateFormatter stringFromDate:event.date], event.name, event.counter];
215 [array addObject:str];
216 }];
217
218 [array sortUsingSelector:@selector(compare:)];
219
220 for (NSString *attribute in self.attributes) {
221 [array addObject:[NSString stringWithFormat:@"attr: %@: %@", attribute, [self.attributes[attribute] description]]];
222 }
223 return array;
224 }
225 }
226
227 @end