archivist_lib/logs/servers/
log_stream.rs1use crate::identity::ComponentIdentity;
5use crate::logs::shared_buffer::FilterCursor;
6use diagnostics_log_encoding::encode::{Encoder, EncoderOpts, ResizableBuffer};
7use diagnostics_log_encoding::{Argument, Header, LOG_CONTROL_BIT, MONIKER, Record, URL};
8use fidl_fuchsia_diagnostics_types::Severity;
9use fuchsia_async as fasync;
10use futures::AsyncWriteExt;
11use std::collections::HashMap;
12use std::future::poll_fn;
13use std::io::Cursor;
14use std::pin::pin;
15use std::sync::Arc;
16use std::task::{Poll, ready};
17use zerocopy::{FromBytes, IntoBytes};
18
19#[derive(thiserror::Error, Debug)]
20pub enum StreamError {
21 #[error(transparent)]
22 Io(#[from] std::io::Error),
23}
24
25pub struct LogStreamServer;
26
27impl LogStreamServer {
28 pub async fn stream_logs_with_manifest(
29 mut socket: fasync::Socket,
30 logs: FilterCursor,
31 ) -> Result<(), StreamError> {
32 let mut logs = pin!(logs);
33 let mut sent_tags = HashMap::new();
34 let mut buffer = Vec::new();
35 while let Some((identity, tag)) = poll_fn(|cx| {
36 loop {
37 match ready!(logs.as_mut().poll_next(cx)) {
38 Some(message) => {
39 let (identity_ref, header, data) = match message.parse() {
40 Ok(res) => res,
41 Err(_) => continue,
42 };
43 let identity = Arc::clone(identity_ref);
44 buffer.clear();
45 buffer.reserve(data.len());
46 buffer.extend_from_slice(header.as_bytes());
47 buffer.extend_from_slice(&data[8..]);
48 return Poll::Ready(Some((identity, header.tag() as u64)));
49 }
50 None => return Poll::Ready(None),
51 }
52 }
53 })
54 .await
55 {
56 let send = match sent_tags.entry(tag) {
57 std::collections::hash_map::Entry::Vacant(e) => {
58 e.insert(Arc::clone(&identity));
59 true
60 }
61 std::collections::hash_map::Entry::Occupied(mut e) => {
62 if !Arc::ptr_eq(e.get(), &identity) && **e.get() != *identity {
63 e.insert(Arc::clone(&identity));
64 true
65 } else {
66 false
67 }
68 }
69 };
70
71 if send {
72 Self::send_component_change(&mut socket, tag, &identity).await?;
73 }
74 socket.write_all(&buffer).await?;
75 }
76 Ok(())
77 }
78
79 async fn send_component_change(
80 socket: &mut fasync::Socket,
81 id: u64,
82 identity: &ComponentIdentity,
83 ) -> Result<(), std::io::Error> {
84 let mut encoder =
85 Encoder::new(Cursor::new(ResizableBuffer::from(Vec::new())), EncoderOpts::default());
86 let record = Record {
87 timestamp: zx::BootInstant::from_nanos(0),
88 severity: Severity::Info.into_primitive(),
89 arguments: vec![
90 Argument::other(MONIKER, identity.moniker.to_string()),
91 Argument::other(URL, identity.url.as_str()),
92 ],
93 };
94 encoder.write_record(record).map_err(std::io::Error::other)?;
95
96 let mut buffer = encoder.take().into_inner().into_inner();
97 if buffer.len() >= 8 {
98 let mut header = Header::read_from_bytes(&buffer[0..8]).unwrap();
99 header.set_tag((id as u32) | LOG_CONTROL_BIT);
100 buffer[0..8].copy_from_slice(header.as_bytes());
101 }
102 socket.write_all(&buffer).await?;
103 Ok(())
104 }
105}
106
107#[derive(Default)]
108pub struct ExtendRecordOpts {
109 pub moniker: bool,
110 pub component_url: bool,
111 pub rolled_out: bool,
112}
113
114impl ExtendRecordOpts {
115 fn should_extend(&self) -> bool {
116 let Self { moniker, component_url, rolled_out } = self;
117 *moniker || *component_url || *rolled_out
118 }
119}
120
121fn padding(len: usize) -> &'static [u8] {
123 &[0; 8][(len + 7) % 8 + 1..]
124}
125
126pub fn extend_fxt_record(
127 identity: &ComponentIdentity,
128 rolled_out: u64,
129 opts: &ExtendRecordOpts,
130 buffer: &mut Vec<u8>,
131) {
132 if !opts.should_extend() {
133 return;
134 }
135
136 let moniker = if opts.moniker { identity.moniker.as_ref() } else { "" };
137 let component_url = if opts.component_url { identity.url.as_ref() } else { "" };
138 let rolled_out_value = if opts.rolled_out { rolled_out } else { 0 };
139
140 let moniker_len = moniker.len() as u32;
141 let component_url_len = component_url.len() as u32;
142
143 buffer.extend_from_slice(&moniker_len.to_le_bytes());
144 buffer.extend_from_slice(&component_url_len.to_le_bytes());
145 buffer.extend_from_slice(&rolled_out_value.to_le_bytes());
146
147 buffer.extend_from_slice(moniker.as_bytes());
148 buffer.extend_from_slice(padding(moniker.len()));
149
150 buffer.extend_from_slice(component_url.as_bytes());
151 buffer.extend_from_slice(padding(component_url.len()));
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use crate::logs::repository::LogsRepository;
158 use crate::logs::testing::make_message;
159 use diagnostics_log_encoding::Value;
160 use diagnostics_log_encoding::parse::parse_record;
161 use fidl_fuchsia_diagnostics::StreamMode;
162 use futures::AsyncReadExt;
163 use moniker::ExtendedMoniker;
164 use test_case::test_case;
165 use zx;
166
167 #[fuchsia::test]
168 async fn log_stream_with_manifest() {
169 let repo = LogsRepository::for_test(fasync::Scope::new());
170 let identity = Arc::new(ComponentIdentity::new(
171 ExtendedMoniker::parse_str("./foo").unwrap(),
172 "fuchsia-pkg://foo",
173 ));
174 let container = repo.get_log_container(Arc::clone(&identity));
175 let container_tag = container.buffer().iob_tag() as u32;
176
177 let scope = fasync::Scope::new();
178 let (client_socket, server_socket) = zx::Socket::create_stream();
179 let mut client_socket = fasync::Socket::from_socket(client_socket);
180 let logs = repo.logs_cursor_raw(StreamMode::SnapshotThenSubscribe, Vec::new());
181 scope.spawn(async move {
182 let _ = LogStreamServer::stream_logs_with_manifest(
183 fasync::Socket::from_socket(server_socket),
184 logs,
185 )
186 .await;
187 });
188
189 container.ingest_message(make_message("a", None, zx::BootInstant::from_nanos(1)));
192
193 let mut buf = vec![0u8; 4096];
194 let mut offset = 0;
195
196 let (manifest_record, manifest_len) = loop {
198 if offset > 0
199 && let Ok((record, rest)) = parse_record(&buf[..offset])
200 {
201 let len = offset - rest.len();
202 break (record, len);
203 }
204 let n = client_socket.read(&mut buf[offset..]).await.expect("read");
205 assert!(n > 0, "socket closed before receiving manifest");
206 offset += n;
207 };
208
209 assert_eq!(manifest_record.arguments[0].name(), "moniker");
211 assert_eq!(manifest_record.arguments[0].value(), Value::Text("foo".into()));
212 assert_eq!(manifest_record.arguments[1].name(), "url");
213 assert_eq!(manifest_record.arguments[1].value(), Value::Text("fuchsia-pkg://foo".into()));
214
215 let (log_record, _log_len) = loop {
217 if offset > manifest_len
218 && let Ok((record, rest)) = parse_record(&buf[manifest_len..offset])
219 {
220 let len = offset - manifest_len - rest.len();
221 break (record, len);
222 }
223 let n = client_socket.read(&mut buf[offset..]).await.expect("read");
224 assert!(n > 0, "socket closed before receiving log record");
225 offset += n;
226 };
227
228 let header1 = Header::read_from_bytes(&buf[0..8]).unwrap();
230 let tag_id = header1.tag();
231 assert_ne!(tag_id & LOG_CONTROL_BIT, 0, "Manifest should have LOG_CONTROL_BIT set");
232
233 let header2 = Header::read_from_bytes(&buf[manifest_len..manifest_len + 8]).unwrap();
235 assert_eq!(
236 header2.tag() & LOG_CONTROL_BIT,
237 0,
238 "Log record should NOT have LOG_CONTROL_BIT set"
239 );
240 assert_eq!(
241 header2.tag(),
242 tag_id & !LOG_CONTROL_BIT,
243 "Log record tag should match Manifest tag ID"
244 );
245 assert_eq!(header2.tag(), container_tag, "Log record tag ID should equal IOB tag ID");
246
247 assert_eq!(log_record.arguments[2].value(), Value::Text("a".into()));
248 }
249
250 #[fuchsia::test]
251 async fn log_stream_with_manifest_reused_tag() {
252 use crate::logs::shared_buffer::create_ring_buffer;
253 let repo = LogsRepository::new(
255 create_ring_buffer(65536),
256 std::iter::empty(),
257 &Default::default(),
258 fasync::Scope::new(),
259 );
260
261 let scope = fasync::Scope::new();
262 let (client_socket, server_socket) = zx::Socket::create_stream();
263 let mut client_socket = fasync::Socket::from_socket(client_socket);
264 let logs = repo.logs_cursor_raw(StreamMode::SnapshotThenSubscribe, Vec::new());
265 scope.spawn(async move {
266 let _ = LogStreamServer::stream_logs_with_manifest(
267 fasync::Socket::from_socket(server_socket),
268 logs,
269 )
270 .await;
271 });
272
273 let identity_a = Arc::new(ComponentIdentity::new(
275 ExtendedMoniker::parse_str("./foo").unwrap(),
276 "fuchsia-pkg://foo",
277 ));
278 let container_a = repo.get_log_container(Arc::clone(&identity_a));
279 let tag_a = container_a.buffer().iob_tag();
280
281 container_a.ingest_message(make_message("msg_a", None, zx::BootInstant::from_nanos(1)));
283
284 let mut buf = vec![0u8; 65536];
285 let mut offset = 0;
286
287 async fn read_one_record(
289 socket: &mut fasync::Socket,
290 buf: &mut [u8],
291 offset: &mut usize,
292 ) -> (diagnostics_log_encoding::Record<'static>, usize) {
293 loop {
294 if *offset > 0
295 && let Ok((record, rest)) = parse_record(&buf[..*offset])
296 {
297 let len = *offset - rest.len();
298 let owned_record = record.into_owned();
299 buf.copy_within(len..*offset, 0);
301 *offset -= len;
302 return (owned_record, len);
304 }
305 let n = socket.read(&mut buf[*offset..]).await.expect("read");
306 assert!(n > 0, "socket closed unexpectedly");
307 *offset += n;
308 }
309 }
310
311 let (manifest_a, _) = read_one_record(&mut client_socket, &mut buf, &mut offset).await;
313 assert_eq!(manifest_a.arguments[0].value(), Value::Text("foo".into()));
314
315 let (log_a, _) = read_one_record(&mut client_socket, &mut buf, &mut offset).await;
316 assert_eq!(log_a.arguments[2].value(), Value::Text("msg_a".into()));
317
318 container_a.mark_stopped();
320 drop(container_a);
321
322 let identity_filler = Arc::new(ComponentIdentity::new(
324 ExtendedMoniker::parse_str("./filler").unwrap(),
325 "fuchsia-pkg://filler",
326 ));
327 let container_filler = repo.get_log_container(Arc::clone(&identity_filler));
328
329 let identity_b = Arc::new(ComponentIdentity::new(
330 ExtendedMoniker::parse_str("./bar").unwrap(),
331 "fuchsia-pkg://bar",
332 ));
333
334 let mut container_b;
335 loop {
336 container_filler.ingest_message(make_message(
338 "fill",
339 None,
340 zx::BootInstant::from_nanos(1),
341 ));
342
343 loop {
347 let mut temp_buf = [0u8; 1024];
348 let read_fut = client_socket.read(&mut temp_buf);
350 match futures::poll!(read_fut) {
351 std::task::Poll::Ready(Ok(n)) if n > 0 => {
352 }
356 _ => break, }
358 }
359
360 container_b = repo.get_log_container(Arc::clone(&identity_b));
362 if container_b.buffer().iob_tag() == tag_a {
363 break;
364 }
365
366 container_b.mark_stopped();
368 drop(container_b);
369
370 fasync::Timer::new(std::time::Duration::from_millis(10)).await;
372 }
373
374 container_b.ingest_message(make_message("msg_b", None, zx::BootInstant::from_nanos(2)));
376
377 loop {
386 let (record, _) = read_one_record(&mut client_socket, &mut buf, &mut offset).await;
387
388 if !record.arguments.is_empty()
390 && record.arguments[0].name() == "moniker"
391 && record.arguments[0].value() == Value::Text("bar".into())
392 {
393 break;
395 }
396 }
398
399 let (log_b, _) = read_one_record(&mut client_socket, &mut buf, &mut offset).await;
401 assert_eq!(log_b.arguments[2].value(), Value::Text("msg_b".into()));
402 }
403
404 #[test_case(ExtendRecordOpts::default(), "", "", 0 ; "no_additional_metadata")]
405 #[test_case(
406 ExtendRecordOpts { moniker: true, ..Default::default() },
407 "UNKNOWN",
408 "",
409 0
410 ; "with_moniker")]
411 #[test_case(
412 ExtendRecordOpts { component_url: true, ..Default::default() },
413 "",
414 "fuchsia-pkg://UNKNOWN",
415 0
416 ; "with_url")]
417 #[test_case(
418 ExtendRecordOpts { rolled_out: true, ..Default::default() },
419 "",
420 "",
421 42
422 ; "with_rolled_out")]
423 #[test_case(
424 ExtendRecordOpts { moniker: true, component_url: true, rolled_out: true },
425 "UNKNOWN",
426 "fuchsia-pkg://UNKNOWN",
427 42
428 ; "with_all")]
429 #[fuchsia::test]
430 fn extend_record_with_metadata(
431 opts: ExtendRecordOpts,
432 expected_moniker: &str,
433 expected_url: &str,
434 expected_rolled_out: u64,
435 ) {
436 let mut buffer = Vec::new();
437 extend_fxt_record(&ComponentIdentity::unknown(), 42, &opts, &mut buffer);
438
439 if !opts.should_extend() {
440 assert!(buffer.is_empty());
441 return;
442 }
443
444 let moniker_len = u32::from_le_bytes(buffer[0..4].try_into().unwrap()) as usize;
445 let component_url_len = u32::from_le_bytes(buffer[4..8].try_into().unwrap()) as usize;
446
447 let rolled_out = u64::from_le_bytes(buffer[8..16].try_into().unwrap());
448 if opts.rolled_out {
449 assert_eq!(rolled_out, expected_rolled_out);
450 } else {
451 assert_eq!(rolled_out, 0);
452 }
453
454 let mut offset = 16;
455 let moniker = std::str::from_utf8(&buffer[offset..offset + moniker_len]).unwrap();
456 assert_eq!(moniker, expected_moniker);
457 let moniker_padded_len = (moniker_len + 7) & !7;
458 offset += moniker_padded_len;
459
460 let url = std::str::from_utf8(&buffer[offset..offset + component_url_len]).unwrap();
461 assert_eq!(url, expected_url);
462 let component_url_padded_len = (component_url_len + 7) & !7;
463 offset += component_url_padded_len;
464
465 assert_eq!(offset, buffer.len());
466 }
467}