1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/trace_processor/importers/archive/gzip_trace_parser.h"
18
19 #include <cstdint>
20 #include <cstring>
21 #include <memory>
22 #include <string>
23 #include <utility>
24
25 #include "perfetto/base/logging.h"
26 #include "perfetto/base/status.h"
27 #include "perfetto/ext/base/string_utils.h"
28 #include "perfetto/ext/base/string_view.h"
29 #include "perfetto/trace_processor/trace_blob.h"
30 #include "perfetto/trace_processor/trace_blob_view.h"
31 #include "src/trace_processor/forwarding_trace_parser.h"
32 #include "src/trace_processor/importers/common/chunked_trace_reader.h"
33 #include "src/trace_processor/importers/common/trace_file_tracker.h"
34 #include "src/trace_processor/types/trace_processor_context.h"
35 #include "src/trace_processor/util/gzip_utils.h"
36 #include "src/trace_processor/util/status_macros.h"
37
38 namespace perfetto::trace_processor {
39
40 namespace {
41
42 using ResultCode = util::GzipDecompressor::ResultCode;
43
44 } // namespace
45
GzipTraceParser(TraceProcessorContext * context)46 GzipTraceParser::GzipTraceParser(TraceProcessorContext* context)
47 : context_(context) {}
48
GzipTraceParser(std::unique_ptr<ChunkedTraceReader> reader)49 GzipTraceParser::GzipTraceParser(std::unique_ptr<ChunkedTraceReader> reader)
50 : context_(nullptr), inner_(std::move(reader)) {}
51
52 GzipTraceParser::~GzipTraceParser() = default;
53
Parse(TraceBlobView blob)54 base::Status GzipTraceParser::Parse(TraceBlobView blob) {
55 return ParseUnowned(blob.data(), blob.size());
56 }
57
ParseUnowned(const uint8_t * data,size_t size)58 base::Status GzipTraceParser::ParseUnowned(const uint8_t* data, size_t size) {
59 const uint8_t* start = data;
60 size_t len = size;
61
62 if (!inner_) {
63 PERFETTO_CHECK(context_);
64 inner_.reset(new ForwardingTraceParser(
65 context_, context_->trace_file_tracker->AddFile("")));
66 }
67
68 if (!first_chunk_parsed_) {
69 // .ctrace files begin with: "TRACE:\n" or "done. TRACE:\n" strip this if
70 // present.
71 base::StringView beginning(reinterpret_cast<const char*>(start), size);
72
73 static const char* kSystraceFileHeader = "TRACE:\n";
74 size_t offset = Find(kSystraceFileHeader, beginning);
75 if (offset != std::string::npos) {
76 start += strlen(kSystraceFileHeader) + offset;
77 len -= strlen(kSystraceFileHeader) + offset;
78 }
79 first_chunk_parsed_ = true;
80 }
81
82 // Our default uncompressed buffer size is 32MB as it allows for good
83 // throughput.
84 constexpr size_t kUncompressedBufferSize = 32ul * 1024 * 1024;
85 decompressor_.Feed(start, len);
86
87 for (;;) {
88 if (!buffer_) {
89 buffer_.reset(new uint8_t[kUncompressedBufferSize]);
90 bytes_written_ = 0;
91 }
92
93 auto result =
94 decompressor_.ExtractOutput(buffer_.get() + bytes_written_,
95 kUncompressedBufferSize - bytes_written_);
96 util::GzipDecompressor::ResultCode ret = result.ret;
97 if (ret == ResultCode::kError)
98 return base::ErrStatus("Failed to decompress trace chunk");
99
100 if (ret == ResultCode::kNeedsMoreInput) {
101 PERFETTO_DCHECK(result.bytes_written == 0);
102 return base::OkStatus();
103 }
104 bytes_written_ += result.bytes_written;
105 output_state_ = kMidStream;
106
107 if (bytes_written_ == kUncompressedBufferSize || ret == ResultCode::kEof) {
108 TraceBlob blob =
109 TraceBlob::TakeOwnership(std::move(buffer_), bytes_written_);
110 RETURN_IF_ERROR(inner_->Parse(TraceBlobView(std::move(blob))));
111 }
112
113 // We support multiple gzip streams in a single gzip file (which is valid
114 // according to RFC1952 section 2.2): in that case, we just need to reset
115 // the decompressor to begin processing the next stream: all other variables
116 // can be preserved.
117 if (ret == ResultCode::kEof) {
118 decompressor_.Reset();
119 output_state_ = kStreamBoundary;
120
121 if (decompressor_.AvailIn() == 0) {
122 return base::OkStatus();
123 }
124 }
125 }
126 }
127
NotifyEndOfFile()128 base::Status GzipTraceParser::NotifyEndOfFile() {
129 if (output_state_ != kStreamBoundary || decompressor_.AvailIn() > 0) {
130 return base::ErrStatus("GZIP stream incomplete, trace is likely corrupt");
131 }
132 PERFETTO_CHECK(!buffer_);
133 return inner_ ? inner_->NotifyEndOfFile() : base::OkStatus();
134 }
135
136 } // namespace perfetto::trace_processor
137