1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use anyhow::Error;
use base64::engine::{general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
use serde_json::{to_value, Value};

use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};

/// Perform Traceutil operations.
///
/// Note this object is shared among all threads created by server.
///
/// This facade does not hold onto a Traceutil proxy as the server may be
/// long-running while individual tests set up and tear down Traceutil.
///
/// WARNING: Use of this facade is discouraged as its functionality is only to download traces that
/// were collected through other means (such as running the trace binary over ssh). Instead, see
/// TracingFacade, which allows for control of the tracing system as well.
#[derive(Debug)]
pub struct TraceutilFacade {}

// Chunk size of 8 MiB. Because we read a full chunk into memory and convert it
// to base64, in cases where the system is close to OOM, a small chunk size
// makes it more likely that we can successfully download the trace. On the other
// hand, a very small chunk size slows down the trace download. Empirically, 8 MiB
// seems to be a reasonable compromise.
const MAX_CHUNK_SIZE: usize = 8 * 1024 * 1024;

impl TraceutilFacade {
    pub fn new() -> TraceutilFacade {
        TraceutilFacade {}
    }

    /// Gets data from the specified path starting from an optional offset.
    ///
    /// Loading and returning the entire file is problematic for large trace files,
    /// so will return up to |MAX_CHUNK_SIZE| bytes at once. The bytes of the file are
    /// returned in a field called |data|. If there is more data to read, then a field
    /// |next_offset| will be returned that indicates where it left off.
    pub async fn get_trace_file(&self, args: Value) -> Result<Value, Error> {
        let path = args.get("path").ok_or(format_err!("GetTraceFile failed, no path"))?;
        let path = path.as_str().ok_or(format_err!("GetTraceFile failed, path not string"))?;
        let offset = args.get("offset").and_then(Value::as_u64).unwrap_or(0);

        let mut file = File::open(path)?;
        file.seek(SeekFrom::Start(offset))?;

        let mut contents = Vec::new();
        file.by_ref().take(MAX_CHUNK_SIZE as u64).read_to_end(&mut contents)?;

        let encoded_contents = BASE64_STANDARD.encode(&contents);

        let mut result: HashMap<String, Value> = HashMap::new();
        result.insert("data".to_owned(), to_value(encoded_contents)?);
        if contents.len() == MAX_CHUNK_SIZE {
            let new_offset = file.seek(SeekFrom::Current(0))?;
            result.insert("next_offset".to_owned(), to_value(new_offset)?);
        }

        Ok(to_value(result)?)
    }
}