Skip to main content

attribution_server/
attribution_server.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fidl::Error::ClientChannelClosed;
6use fidl_fuchsia_memory_attribution as fattribution;
7use fuchsia_sync::Mutex;
8use log::error;
9use measure_tape_for_attribution::Measurable;
10use std::collections::HashMap;
11use std::sync::Arc;
12use thiserror::Error;
13
14mod key {
15    /// Identifier used for disambiguation;
16    #[derive(PartialEq, Eq, Clone, Copy)]
17    pub struct Key(u64);
18
19    /// Generates unique [Key] objects.
20    pub struct KeyGenerator {
21        next: Key,
22    }
23
24    impl Default for KeyGenerator {
25        fn default() -> Self {
26            Self { next: Key(0) }
27        }
28    }
29
30    impl KeyGenerator {
31        /// Generates the next [Key] object.
32        pub fn next(&mut self) -> Key {
33            let next_key = self.next;
34            self.next = Key(self.next.0.checked_add(1).expect("Key generator overflow"));
35            next_key
36        }
37    }
38}
39
40/// Function of this type returns a vector of attribution updates, and is used
41/// as the type of the callback in [AttributionServer::new].
42type GetAttributionFn = dyn Fn() -> Vec<fattribution::AttributionUpdate> + Send;
43
44/// Error types that may be used by the async hanging-get server.
45#[derive(Error, Debug)]
46pub enum AttributionServerObservationError {
47    #[error("multiple pending observations for the same Observer")]
48    GetUpdateAlreadyPending,
49}
50
51#[derive(Clone, PartialEq, Eq, Hash)]
52struct PrincipalIdentifier(u64);
53
54/// Main structure for the memory attribution hanging get server.
55///
56/// Components that wish to expose attribution information should create a
57/// single [AttributionServer] object.
58/// Each inbound fuchsia.attribution.Provider connection should get its own
59/// [Observer] object using [AttributionServer::new_observer].
60/// [Publisher]s, created using [AttributionServer::new_publisher], should be
61/// used to push attribution changes.
62#[derive(Clone)]
63pub struct AttributionServerHandle {
64    inner: Arc<Mutex<AttributionServer>>,
65}
66
67impl AttributionServerHandle {
68    /// Create a new [Observer] that represents a single client.
69    ///
70    /// Each FIDL client connection should get its own [Observer] object.
71    pub fn new_observer(&self, control_handle: fattribution::ProviderControlHandle) -> Observer {
72        AttributionServer::register(&self.inner, control_handle)
73    }
74
75    /// Create a new [Publisher] that can push updates to observers.
76    pub fn new_publisher(&self) -> Publisher {
77        Publisher { inner: self.inner.clone() }
78    }
79}
80
81/// An `Observer` can be used to register observation requests, corresponding to individual hanging
82/// get calls. These will be notified when the state changes or immediately the first time
83/// an `Observer` registers an observation.
84pub struct Observer {
85    inner: Arc<Mutex<AttributionServer>>,
86    subscription_id: key::Key,
87}
88
89impl Observer {
90    /// Register a new observation request.
91    ///
92    /// A newly-created observer will first receive the current state. After
93    /// the first call, the observer will be notified only if the state changes.
94    ///
95    /// Errors occur when an Observer attempts to wait for an update when there
96    /// is a update request already pending.
97    pub fn next(&self, responder: fattribution::ProviderGetResponder) {
98        self.inner.lock().next(responder)
99    }
100}
101
102impl Drop for Observer {
103    fn drop(&mut self) {
104        self.inner.lock().unregister(self.subscription_id);
105    }
106}
107
108/// A [Publisher] should be used to send updates to [Observer]s.
109pub struct Publisher {
110    inner: Arc<Mutex<AttributionServer>>,
111}
112
113impl Publisher {
114    /// Registers an update to the state observed.
115    ///
116    /// `partial_state` is a function that returns the update.
117    pub fn on_update(&self, updates: Vec<fattribution::AttributionUpdate>) {
118        // [update_generator] is a `Fn` and not an `FnOnce` in order to be called multiple times,
119        // once for each [Observer].
120        self.inner.lock().on_update(updates)
121    }
122}
123
124pub struct AttributionServer {
125    state: Box<GetAttributionFn>,
126    consumer: Option<AttributionConsumer>,
127    key_generator: key::KeyGenerator,
128}
129
130impl AttributionServer {
131    /// Create a new memory attribution server.
132    ///
133    /// `state` is a function returning the complete attribution state (not partial updates).
134    pub fn new(state: Box<GetAttributionFn>) -> AttributionServerHandle {
135        AttributionServerHandle {
136            inner: Arc::new(Mutex::new(AttributionServer {
137                state,
138                consumer: None,
139                key_generator: Default::default(),
140            })),
141        }
142    }
143
144    pub fn on_update(&mut self, updates: Vec<fattribution::AttributionUpdate>) {
145        if let Some(consumer) = &mut self.consumer {
146            return consumer.update_and_notify(updates);
147        }
148    }
149
150    /// Get the next attribution state.
151    pub fn next(&mut self, responder: fattribution::ProviderGetResponder) {
152        let entry = self.consumer.as_mut().unwrap();
153        entry.get_update(responder, self.state.as_ref());
154    }
155
156    pub fn register(
157        inner: &Arc<Mutex<Self>>,
158        control_handle: fattribution::ProviderControlHandle,
159    ) -> Observer {
160        let mut locked_inner = inner.lock();
161
162        if locked_inner.consumer.is_some() {
163            log::warn!("Multiple connection requests to AttributionProvider");
164            // The shutdown of the observer will be done when the old [AttributionConsumer] is
165            // dropped.
166        }
167
168        let key = locked_inner.key_generator.next();
169
170        locked_inner.consumer = Some(AttributionConsumer::new(control_handle, key.clone()));
171        Observer { inner: inner.clone(), subscription_id: key }
172    }
173
174    /// Deregister the current observer. No observer can be registered as long
175    /// as another observer is already registered.
176    pub fn unregister(&mut self, key: key::Key) {
177        if let Some(consumer) = &self.consumer {
178            if consumer.subscription_id == key {
179                self.consumer = None;
180            }
181        }
182    }
183}
184
185/// CoalescedUpdate contains all the pending updates for a given principal.
186#[derive(Default)]
187struct CoalescedUpdate {
188    add: Option<fattribution::AttributionUpdate>,
189    update: Option<fattribution::AttributionUpdate>,
190    remove: Option<fattribution::AttributionUpdate>,
191}
192
193/// Should the update be kept, or can it be discarded.
194#[derive(PartialEq)]
195enum ShouldKeepUpdate {
196    KEEP,
197    DISCARD,
198}
199
200impl CoalescedUpdate {
201    /// Merges updates of a given Principal, discarding the ones that become irrelevant.
202    pub fn update(&mut self, u: fattribution::AttributionUpdate) -> ShouldKeepUpdate {
203        match u {
204            fattribution::AttributionUpdate::Add(u) => {
205                self.add = Some(fattribution::AttributionUpdate::Add(u));
206                self.update = None;
207                self.remove = None;
208            }
209            fattribution::AttributionUpdate::Update(u) => {
210                self.update = Some(fattribution::AttributionUpdate::Update(u));
211            }
212            fattribution::AttributionUpdate::Remove(u) => {
213                if self.add.is_some() {
214                    // We both added and removed the principal, so it is a no-op.
215                    return ShouldKeepUpdate::DISCARD;
216                }
217                self.remove = Some(fattribution::AttributionUpdate::Remove(u));
218            }
219            fattribution::AttributionUpdateUnknown!() => {
220                error!("Unknown attribution update type");
221            }
222        };
223        ShouldKeepUpdate::KEEP
224    }
225
226    pub fn get_updates(self) -> Vec<fattribution::AttributionUpdate> {
227        let mut result = Vec::new();
228        if let Some(u) = self.add {
229            result.push(u);
230        }
231        if let Some(u) = self.update {
232            result.push(u);
233        }
234        if let Some(u) = self.remove {
235            result.push(u);
236        }
237        result
238    }
239
240    pub fn size(&self) -> (usize, usize) {
241        let (mut bytes, mut handles) = (0, 0);
242        if let Some(u) = &self.add {
243            let m = u.measure();
244            bytes += m.num_bytes;
245            handles += m.num_handles;
246        }
247        if let Some(u) = &self.update {
248            let m = u.measure();
249            bytes += m.num_bytes;
250            handles += m.num_handles;
251        }
252        if let Some(u) = &self.remove {
253            let m = u.measure();
254            bytes += m.num_bytes;
255            handles += m.num_handles;
256        }
257        (bytes, handles)
258    }
259}
260
261/// AttributionConsumer tracks pending updates and observation requests for a given id.
262struct AttributionConsumer {
263    /// Whether we sent the first full state, or not.
264    first: bool,
265
266    /// Pending updates waiting to be sent.
267    pending: HashMap<PrincipalIdentifier, CoalescedUpdate>,
268
269    /// Control handle for the FIDL connection.
270    observer_control_handle: fattribution::ProviderControlHandle,
271
272    /// FIDL responder for a pending hanging get call.
273    responder: Option<fattribution::ProviderGetResponder>,
274
275    /// Matches an AttributionConsumer with an Observer.
276    subscription_id: key::Key,
277}
278
279impl Drop for AttributionConsumer {
280    fn drop(&mut self) {
281        self.observer_control_handle.shutdown_with_epitaph(zx::Status::CANCELED);
282    }
283}
284
285impl AttributionConsumer {
286    /// Create a new [AttributionConsumer] without an `observer` and an initial `dirty`
287    /// value of `true`.
288    pub fn new(
289        observer_control_handle: fattribution::ProviderControlHandle,
290        key: key::Key,
291    ) -> Self {
292        AttributionConsumer {
293            first: true,
294            pending: HashMap::new(),
295            observer_control_handle: observer_control_handle,
296            responder: None,
297            subscription_id: key,
298        }
299    }
300
301    /// Register a new observation request. The observer will be notified immediately if
302    /// the [AttributionConsumer] has pending updates, or hasn't sent anything yet. The
303    /// request will be stored for future notification if the [AttributionConsumer] does
304    /// not have anything to send yet.
305    pub fn get_update(
306        &mut self,
307        responder: fattribution::ProviderGetResponder,
308        gen_state: &GetAttributionFn,
309    ) {
310        if self.responder.is_some() {
311            self.observer_control_handle.shutdown_with_epitaph(zx::Status::BAD_STATE);
312            return;
313        }
314        if self.first {
315            self.first = false;
316            self.pending.clear();
317            self.responder = Some(responder);
318            self.update_and_notify(gen_state());
319            return;
320        }
321        self.responder = Some(responder);
322        self.maybe_notify();
323    }
324
325    /// Take in new memory attribution updates.
326    pub fn update_and_notify(&mut self, updated_state: Vec<fattribution::AttributionUpdate>) {
327        for update in updated_state {
328            let principal: PrincipalIdentifier = match &update {
329                fattribution::AttributionUpdate::Add(added_attribution) => {
330                    PrincipalIdentifier(added_attribution.identifier.unwrap())
331                }
332                fattribution::AttributionUpdate::Update(update_attribution) => {
333                    PrincipalIdentifier(update_attribution.identifier.unwrap())
334                }
335                fattribution::AttributionUpdate::Remove(remove_attribution) => {
336                    PrincipalIdentifier(*remove_attribution)
337                }
338                &fattribution::AttributionUpdateUnknown!() => {
339                    unimplemented!()
340                }
341            };
342            if self.pending.entry(principal.clone()).or_insert(Default::default()).update(update)
343                == ShouldKeepUpdate::DISCARD
344            {
345                self.pending.remove(&principal);
346            }
347        }
348        self.maybe_notify();
349    }
350
351    /// Notify of the pending updates if a responder is available.
352    fn maybe_notify(&mut self) {
353        if self.pending.is_empty() {
354            return;
355        }
356
357        match self.responder.take() {
358            Some(observer) => {
359                let mut iterator = self.pending.drain().peekable();
360                let mut current_size: usize = 32;
361                let mut current_handles: usize = 0;
362                let mut update = Vec::new();
363                while let Some((_, next)) = iterator.peek() {
364                    let (update_size, update_handles) = next.size();
365
366                    if current_size + update_size > zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize {
367                        break;
368                    }
369                    if current_handles + update_handles
370                        > zx::sys::ZX_CHANNEL_MAX_MSG_HANDLES as usize
371                    {
372                        break;
373                    }
374                    current_size += update_size;
375                    current_handles += update_handles;
376                    update.extend(iterator.next().unwrap().1.get_updates().into_iter());
377                }
378
379                self.pending = iterator.collect();
380                Self::send_update(update, observer)
381            }
382            None => {}
383        }
384    }
385
386    /// Sends the attribution update to the provided responder.
387    fn send_update(
388        state: Vec<fattribution::AttributionUpdate>,
389        responder: fattribution::ProviderGetResponder,
390    ) {
391        match responder.send(Ok(fattribution::ProviderGetResponse {
392            attributions: Some(state),
393            ..Default::default()
394        })) {
395            Ok(()) => {} // indicates that the observer was successfully updated
396            Err(e) => {
397                // `send()` ensures that the channel is shut down in case of error.
398                if let ClientChannelClosed { epitaph: fidl::Epitaph::PeerClosed, .. } = e {
399                    // Skip if this is simply our client closing the channel.
400                    return;
401                }
402                error!("Failed to send memory state to observer: {}", e);
403            }
404        }
405    }
406}
407
408#[cfg(test)]
409mod tests {
410    use assert_matches::assert_matches;
411
412    use super::*;
413    use fidl::endpoints::RequestStream;
414    use fuchsia_async as fasync;
415    use futures::TryStreamExt;
416
417    /// Tests that the ELF runner can tell us about the resources used by the component it runs.
418    #[test]
419    fn test_attribute_memory() {
420        let mut exec = fasync::TestExecutor::new();
421        let server = AttributionServer::new(Box::new(|| {
422            let new_principal = fattribution::NewPrincipal {
423                identifier: Some(0),
424                description: Some(fattribution::Description::Part("part".to_owned())),
425                principal_type: Some(fattribution::PrincipalType::Runnable),
426                detailed_attribution: None,
427                __source_breaking: fidl::marker::SourceBreaking,
428            };
429            vec![fattribution::AttributionUpdate::Add(new_principal)]
430        }));
431        let (snapshot_provider, snapshot_request_stream) =
432            fidl::endpoints::create_proxy_and_stream::<fattribution::ProviderMarker>();
433
434        let observer = server.new_observer(snapshot_request_stream.control_handle());
435        fasync::Task::spawn(async move {
436            serve(observer, snapshot_request_stream).await.unwrap();
437        })
438        .detach();
439
440        let attributions =
441            exec.run_singlethreaded(snapshot_provider.get()).unwrap().unwrap().attributions;
442        assert!(attributions.is_some());
443
444        let attributions_vec = attributions.unwrap();
445        // It should contain one component, the one we just launched.
446        assert_eq!(attributions_vec.len(), 1);
447        let new_attrib = attributions_vec.get(0).unwrap();
448        let fattribution::AttributionUpdate::Add(added_principal) = new_attrib else {
449            panic!("Not a new principal");
450        };
451        assert_eq!(added_principal.identifier, Some(0));
452        assert_eq!(added_principal.principal_type, Some(fattribution::PrincipalType::Runnable));
453
454        server.new_publisher().on_update(vec![fattribution::AttributionUpdate::Update(
455            fattribution::UpdatedPrincipal { identifier: Some(0), ..Default::default() },
456        )]);
457        let attributions =
458            exec.run_singlethreaded(snapshot_provider.get()).unwrap().unwrap().attributions;
459        assert!(attributions.is_some());
460
461        let attributions_vec = attributions.unwrap();
462        // It should contain one component, the one we just launched.
463        assert_eq!(attributions_vec.len(), 1);
464        let updated_attrib = attributions_vec.get(0).unwrap();
465        let fattribution::AttributionUpdate::Update(updated_principal) = updated_attrib else {
466            panic!("Not an updated principal");
467        };
468        assert_eq!(updated_principal.identifier, Some(0));
469    }
470
471    pub async fn serve(
472        observer: Observer,
473        mut stream: fattribution::ProviderRequestStream,
474    ) -> Result<(), fidl::Error> {
475        while let Some(request) = stream.try_next().await? {
476            match request {
477                fattribution::ProviderRequest::Get { responder } => {
478                    observer.next(responder);
479                }
480                fattribution::ProviderRequest::_UnknownMethod { .. } => {
481                    assert!(false);
482                }
483            }
484        }
485        Ok(())
486    }
487
488    /// Tests that a new Provider connection cancels a previous one.
489    #[test]
490    fn test_disconnect_on_new_connection() {
491        let mut exec = fasync::TestExecutor::new();
492        let server = AttributionServer::new(Box::new(|| {
493            vec![fattribution::AttributionUpdate::Add(fattribution::NewPrincipal {
494                identifier: Some(1),
495                description: Some(fattribution::Description::Part("part1".to_owned())),
496                principal_type: Some(fattribution::PrincipalType::Runnable),
497                detailed_attribution: None,
498                ..Default::default()
499            })]
500        }));
501        let (snapshot_provider, snapshot_request_stream) =
502            fidl::endpoints::create_proxy_and_stream::<fattribution::ProviderMarker>();
503
504        let observer = server.new_observer(snapshot_request_stream.control_handle());
505
506        let (new_snapshot_provider, new_snapshot_request_stream) =
507            fidl::endpoints::create_proxy_and_stream::<fattribution::ProviderMarker>();
508
509        let new_observer = server.new_observer(new_snapshot_request_stream.control_handle());
510        fasync::Task::spawn(async move {
511            serve(new_observer, new_snapshot_request_stream).await.unwrap();
512        })
513        .detach();
514
515        drop(observer);
516        let result = exec.run_singlethreaded(snapshot_provider.get());
517        assert_matches!(
518            result,
519            Err(ClientChannelClosed { epitaph, .. }) if epitaph == zx::Status::CANCELED
520        );
521
522        let result = exec.run_singlethreaded(new_snapshot_provider.get());
523        assert!(result.is_ok());
524        server.new_publisher().on_update(vec![fattribution::AttributionUpdate::Add(
525            fattribution::NewPrincipal {
526                identifier: Some(2),
527                description: Some(fattribution::Description::Part("part2".to_owned())),
528                principal_type: Some(fattribution::PrincipalType::Runnable),
529                detailed_attribution: None,
530                ..Default::default()
531            },
532        )]);
533        let result = exec.run_singlethreaded(new_snapshot_provider.get());
534        assert!(result.is_ok());
535    }
536
537    /// Tests that a new [Provider::get] call while another call is still pending
538    /// generates an error.
539    #[test]
540    fn test_disconnect_on_two_pending_gets() {
541        let mut exec = fasync::TestExecutor::new();
542        let server = AttributionServer::new(Box::new(|| {
543            let new_principal = fattribution::NewPrincipal {
544                identifier: Some(0),
545                principal_type: Some(fattribution::PrincipalType::Runnable),
546                ..Default::default()
547            };
548            vec![fattribution::AttributionUpdate::Add(new_principal)]
549        }));
550        let (snapshot_provider, snapshot_request_stream) =
551            fidl::endpoints::create_proxy_and_stream::<fattribution::ProviderMarker>();
552
553        let observer = server.new_observer(snapshot_request_stream.control_handle());
554        fasync::Task::spawn(async move {
555            serve(observer, snapshot_request_stream).await.unwrap();
556        })
557        .detach();
558
559        // The first call should succeed right away.
560        exec.run_singlethreaded(snapshot_provider.get())
561            .expect("Connection dropped")
562            .expect("Get call failed");
563
564        // The next call should block until an update is pushed on the provider side.
565        let mut future = snapshot_provider.get();
566
567        let _ = exec.run_until_stalled(&mut future);
568
569        // The second parallel get() call should fail.
570        let result = exec.run_singlethreaded(snapshot_provider.get());
571
572        let result2 = exec.run_singlethreaded(future);
573
574        assert_matches!(
575            result2,
576            Err(ClientChannelClosed { epitaph, .. }) if epitaph == zx::Status::BAD_STATE
577        );
578        assert_matches!(
579            result,
580            Err(ClientChannelClosed { epitaph, .. }) if epitaph == zx::Status::BAD_STATE
581        );
582    }
583
584    /// Tests that the first get call returns the full state, not updates.
585    #[test]
586    fn test_no_update_on_first_call() {
587        let mut exec = fasync::TestExecutor::new();
588        let server = AttributionServer::new(Box::new(|| {
589            let new_principal = fattribution::NewPrincipal {
590                identifier: Some(0),
591                principal_type: Some(fattribution::PrincipalType::Runnable),
592                ..Default::default()
593            };
594            vec![fattribution::AttributionUpdate::Add(new_principal)]
595        }));
596        let (snapshot_provider, snapshot_request_stream) =
597            fidl::endpoints::create_proxy_and_stream::<fattribution::ProviderMarker>();
598
599        let observer = server.new_observer(snapshot_request_stream.control_handle());
600        fasync::Task::spawn(async move {
601            serve(observer, snapshot_request_stream).await.unwrap();
602        })
603        .detach();
604
605        server.new_publisher().on_update(vec![fattribution::AttributionUpdate::Update(
606            fattribution::UpdatedPrincipal { identifier: Some(0), ..Default::default() },
607        )]);
608
609        // As this is the first call, we should get the full state, not the update.
610        let attributions =
611            exec.run_singlethreaded(snapshot_provider.get()).unwrap().unwrap().attributions;
612        assert!(attributions.is_some());
613
614        let attributions_vec = attributions.unwrap();
615        // It should contain one component, the one we just launched.
616        assert_eq!(attributions_vec.len(), 1);
617        let new_attrib = attributions_vec.get(0).unwrap();
618        let fattribution::AttributionUpdate::Add(added_principal) = new_attrib else {
619            panic!("Not a new principal");
620        };
621        assert_eq!(added_principal.identifier, Some(0));
622        assert_eq!(added_principal.principal_type, Some(fattribution::PrincipalType::Runnable));
623    }
624}