181ad6265SDimitry Andric //===-- PerfContextSwitchDecoder.cpp --======------------------------------===//
281ad6265SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
381ad6265SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
481ad6265SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
581ad6265SDimitry Andric //
681ad6265SDimitry Andric //===----------------------------------------------------------------------===//
781ad6265SDimitry Andric
881ad6265SDimitry Andric #include "PerfContextSwitchDecoder.h"
9*bdd1243dSDimitry Andric #include <optional>
1081ad6265SDimitry Andric
1181ad6265SDimitry Andric using namespace lldb;
1281ad6265SDimitry Andric using namespace lldb_private;
1381ad6265SDimitry Andric using namespace lldb_private::trace_intel_pt;
1481ad6265SDimitry Andric using namespace llvm;
1581ad6265SDimitry Andric
1681ad6265SDimitry Andric /// Copied from <linux/perf_event.h> to avoid depending on perf_event.h on
1781ad6265SDimitry Andric /// non-linux platforms.
1881ad6265SDimitry Andric /// \{
1981ad6265SDimitry Andric #define PERF_RECORD_MISC_SWITCH_OUT (1 << 13)
20753f127fSDimitry Andric
21753f127fSDimitry Andric #define PERF_RECORD_LOST 2
22753f127fSDimitry Andric #define PERF_RECORD_THROTTLE 5
23753f127fSDimitry Andric #define PERF_RECORD_UNTHROTTLE 6
24753f127fSDimitry Andric #define PERF_RECORD_LOST_SAMPLES 13
2581ad6265SDimitry Andric #define PERF_RECORD_SWITCH_CPU_WIDE 15
26753f127fSDimitry Andric #define PERF_RECORD_MAX 19
2781ad6265SDimitry Andric
2881ad6265SDimitry Andric struct perf_event_header {
2981ad6265SDimitry Andric uint32_t type;
3081ad6265SDimitry Andric uint16_t misc;
3181ad6265SDimitry Andric uint16_t size;
3281ad6265SDimitry Andric
3381ad6265SDimitry Andric /// \return
3481ad6265SDimitry Andric /// An \a llvm::Error if the record looks obviously wrong, or \a
3581ad6265SDimitry Andric /// llvm::Error::success() otherwise.
SanityCheckperf_event_header3681ad6265SDimitry Andric Error SanityCheck() const {
3781ad6265SDimitry Andric // The following checks are based on visual inspection of the records and
3881ad6265SDimitry Andric // enums in
3981ad6265SDimitry Andric // https://elixir.bootlin.com/linux/v4.8/source/include/uapi/linux/perf_event.h
4081ad6265SDimitry Andric // See PERF_RECORD_MAX, PERF_RECORD_SWITCH and the data similar records
4181ad6265SDimitry Andric // hold.
4281ad6265SDimitry Andric
4381ad6265SDimitry Andric // A record of too many uint64_t's or more should mean that the data is
4481ad6265SDimitry Andric // wrong
4581ad6265SDimitry Andric const uint64_t max_valid_size_bytes = 8000;
4681ad6265SDimitry Andric if (size == 0 || size > max_valid_size_bytes)
4781ad6265SDimitry Andric return createStringError(
4881ad6265SDimitry Andric inconvertibleErrorCode(),
4981ad6265SDimitry Andric formatv("A record of {0} bytes was found.", size));
5081ad6265SDimitry Andric
5181ad6265SDimitry Andric // We add some numbers to PERF_RECORD_MAX because some systems might have
5281ad6265SDimitry Andric // custom records. In any case, we are looking only for abnormal data.
5381ad6265SDimitry Andric if (type >= PERF_RECORD_MAX + 100)
5481ad6265SDimitry Andric return createStringError(
5581ad6265SDimitry Andric inconvertibleErrorCode(),
5681ad6265SDimitry Andric formatv("Invalid record type {0} was found.", type));
5781ad6265SDimitry Andric return Error::success();
5881ad6265SDimitry Andric }
5981ad6265SDimitry Andric
IsContextSwitchRecordperf_event_header6081ad6265SDimitry Andric bool IsContextSwitchRecord() const {
6181ad6265SDimitry Andric return type == PERF_RECORD_SWITCH_CPU_WIDE;
6281ad6265SDimitry Andric }
63753f127fSDimitry Andric
IsErrorRecordperf_event_header64753f127fSDimitry Andric bool IsErrorRecord() const {
65753f127fSDimitry Andric return type == PERF_RECORD_LOST || type == PERF_RECORD_THROTTLE ||
66753f127fSDimitry Andric type == PERF_RECORD_UNTHROTTLE || type == PERF_RECORD_LOST_SAMPLES;
67753f127fSDimitry Andric }
6881ad6265SDimitry Andric };
6981ad6265SDimitry Andric /// \}
7081ad6265SDimitry Andric
7181ad6265SDimitry Andric /// Record found in the perf_event context switch traces. It might contain
7281ad6265SDimitry Andric /// additional fields in memory, but header.size should have the actual size
7381ad6265SDimitry Andric /// of the record.
7481ad6265SDimitry Andric struct PerfContextSwitchRecord {
7581ad6265SDimitry Andric struct perf_event_header header;
7681ad6265SDimitry Andric uint32_t next_prev_pid;
7781ad6265SDimitry Andric uint32_t next_prev_tid;
7881ad6265SDimitry Andric uint32_t pid, tid;
7981ad6265SDimitry Andric uint64_t time_in_nanos;
8081ad6265SDimitry Andric
IsOutPerfContextSwitchRecord8181ad6265SDimitry Andric bool IsOut() const { return header.misc & PERF_RECORD_MISC_SWITCH_OUT; }
8281ad6265SDimitry Andric };
8381ad6265SDimitry Andric
8481ad6265SDimitry Andric /// Record produced after parsing the raw context switch trace produce by
8581ad6265SDimitry Andric /// perf_event. A major difference between this struct and
8681ad6265SDimitry Andric /// PerfContextSwitchRecord is that this one uses tsc instead of nanos.
8781ad6265SDimitry Andric struct ContextSwitchRecord {
8881ad6265SDimitry Andric uint64_t tsc;
8981ad6265SDimitry Andric /// Whether the switch is in or out
9081ad6265SDimitry Andric bool is_out;
9181ad6265SDimitry Andric /// pid = 0 and tid = 0 indicate the swapper or idle process, which normally
9281ad6265SDimitry Andric /// runs after a context switch out of a normal user thread.
9381ad6265SDimitry Andric lldb::pid_t pid;
9481ad6265SDimitry Andric lldb::tid_t tid;
9581ad6265SDimitry Andric
IsOutContextSwitchRecord9681ad6265SDimitry Andric bool IsOut() const { return is_out; }
9781ad6265SDimitry Andric
IsInContextSwitchRecord9881ad6265SDimitry Andric bool IsIn() const { return !is_out; }
9981ad6265SDimitry Andric };
10081ad6265SDimitry Andric
GetLowestKnownTSC() const10181ad6265SDimitry Andric uint64_t ThreadContinuousExecution::GetLowestKnownTSC() const {
10281ad6265SDimitry Andric switch (variant) {
10381ad6265SDimitry Andric case Variant::Complete:
10481ad6265SDimitry Andric return tscs.complete.start;
10581ad6265SDimitry Andric case Variant::OnlyStart:
10681ad6265SDimitry Andric return tscs.only_start.start;
10781ad6265SDimitry Andric case Variant::OnlyEnd:
10881ad6265SDimitry Andric return tscs.only_end.end;
10981ad6265SDimitry Andric case Variant::HintedEnd:
11081ad6265SDimitry Andric return tscs.hinted_end.start;
11181ad6265SDimitry Andric case Variant::HintedStart:
11281ad6265SDimitry Andric return tscs.hinted_start.end;
11381ad6265SDimitry Andric }
11481ad6265SDimitry Andric }
11581ad6265SDimitry Andric
GetStartTSC() const11681ad6265SDimitry Andric uint64_t ThreadContinuousExecution::GetStartTSC() const {
11781ad6265SDimitry Andric switch (variant) {
11881ad6265SDimitry Andric case Variant::Complete:
11981ad6265SDimitry Andric return tscs.complete.start;
12081ad6265SDimitry Andric case Variant::OnlyStart:
12181ad6265SDimitry Andric return tscs.only_start.start;
12281ad6265SDimitry Andric case Variant::OnlyEnd:
12381ad6265SDimitry Andric return 0;
12481ad6265SDimitry Andric case Variant::HintedEnd:
12581ad6265SDimitry Andric return tscs.hinted_end.start;
12681ad6265SDimitry Andric case Variant::HintedStart:
12781ad6265SDimitry Andric return tscs.hinted_start.hinted_start;
12881ad6265SDimitry Andric }
12981ad6265SDimitry Andric }
13081ad6265SDimitry Andric
GetEndTSC() const13181ad6265SDimitry Andric uint64_t ThreadContinuousExecution::GetEndTSC() const {
13281ad6265SDimitry Andric switch (variant) {
13381ad6265SDimitry Andric case Variant::Complete:
13481ad6265SDimitry Andric return tscs.complete.end;
13581ad6265SDimitry Andric case Variant::OnlyStart:
13681ad6265SDimitry Andric return std::numeric_limits<uint64_t>::max();
13781ad6265SDimitry Andric case Variant::OnlyEnd:
13881ad6265SDimitry Andric return tscs.only_end.end;
13981ad6265SDimitry Andric case Variant::HintedEnd:
14081ad6265SDimitry Andric return tscs.hinted_end.hinted_end;
14181ad6265SDimitry Andric case Variant::HintedStart:
14281ad6265SDimitry Andric return tscs.hinted_start.end;
14381ad6265SDimitry Andric }
14481ad6265SDimitry Andric }
14581ad6265SDimitry Andric
CreateCompleteExecution(lldb::cpu_id_t cpu_id,lldb::tid_t tid,lldb::pid_t pid,uint64_t start,uint64_t end)14681ad6265SDimitry Andric ThreadContinuousExecution ThreadContinuousExecution::CreateCompleteExecution(
14781ad6265SDimitry Andric lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start,
14881ad6265SDimitry Andric uint64_t end) {
14981ad6265SDimitry Andric ThreadContinuousExecution o(cpu_id, tid, pid);
15081ad6265SDimitry Andric o.variant = Variant::Complete;
15181ad6265SDimitry Andric o.tscs.complete.start = start;
15281ad6265SDimitry Andric o.tscs.complete.end = end;
15381ad6265SDimitry Andric return o;
15481ad6265SDimitry Andric }
15581ad6265SDimitry Andric
CreateHintedStartExecution(lldb::cpu_id_t cpu_id,lldb::tid_t tid,lldb::pid_t pid,uint64_t hinted_start,uint64_t end)15681ad6265SDimitry Andric ThreadContinuousExecution ThreadContinuousExecution::CreateHintedStartExecution(
15781ad6265SDimitry Andric lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid,
15881ad6265SDimitry Andric uint64_t hinted_start, uint64_t end) {
15981ad6265SDimitry Andric ThreadContinuousExecution o(cpu_id, tid, pid);
16081ad6265SDimitry Andric o.variant = Variant::HintedStart;
16181ad6265SDimitry Andric o.tscs.hinted_start.hinted_start = hinted_start;
16281ad6265SDimitry Andric o.tscs.hinted_start.end = end;
16381ad6265SDimitry Andric return o;
16481ad6265SDimitry Andric }
16581ad6265SDimitry Andric
CreateHintedEndExecution(lldb::cpu_id_t cpu_id,lldb::tid_t tid,lldb::pid_t pid,uint64_t start,uint64_t hinted_end)16681ad6265SDimitry Andric ThreadContinuousExecution ThreadContinuousExecution::CreateHintedEndExecution(
16781ad6265SDimitry Andric lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start,
16881ad6265SDimitry Andric uint64_t hinted_end) {
16981ad6265SDimitry Andric ThreadContinuousExecution o(cpu_id, tid, pid);
17081ad6265SDimitry Andric o.variant = Variant::HintedEnd;
17181ad6265SDimitry Andric o.tscs.hinted_end.start = start;
17281ad6265SDimitry Andric o.tscs.hinted_end.hinted_end = hinted_end;
17381ad6265SDimitry Andric return o;
17481ad6265SDimitry Andric }
17581ad6265SDimitry Andric
CreateOnlyEndExecution(lldb::cpu_id_t cpu_id,lldb::tid_t tid,lldb::pid_t pid,uint64_t end)17681ad6265SDimitry Andric ThreadContinuousExecution ThreadContinuousExecution::CreateOnlyEndExecution(
17781ad6265SDimitry Andric lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t end) {
17881ad6265SDimitry Andric ThreadContinuousExecution o(cpu_id, tid, pid);
17981ad6265SDimitry Andric o.variant = Variant::OnlyEnd;
18081ad6265SDimitry Andric o.tscs.only_end.end = end;
18181ad6265SDimitry Andric return o;
18281ad6265SDimitry Andric }
18381ad6265SDimitry Andric
CreateOnlyStartExecution(lldb::cpu_id_t cpu_id,lldb::tid_t tid,lldb::pid_t pid,uint64_t start)18481ad6265SDimitry Andric ThreadContinuousExecution ThreadContinuousExecution::CreateOnlyStartExecution(
18581ad6265SDimitry Andric lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start) {
18681ad6265SDimitry Andric ThreadContinuousExecution o(cpu_id, tid, pid);
18781ad6265SDimitry Andric o.variant = Variant::OnlyStart;
18881ad6265SDimitry Andric o.tscs.only_start.start = start;
18981ad6265SDimitry Andric return o;
19081ad6265SDimitry Andric }
19181ad6265SDimitry Andric
RecoverExecutionsFromConsecutiveRecords(cpu_id_t cpu_id,const LinuxPerfZeroTscConversion & tsc_conversion,const ContextSwitchRecord & current_record,const std::optional<ContextSwitchRecord> & prev_record,std::function<void (const ThreadContinuousExecution & execution)> on_new_execution)19281ad6265SDimitry Andric static Error RecoverExecutionsFromConsecutiveRecords(
19381ad6265SDimitry Andric cpu_id_t cpu_id, const LinuxPerfZeroTscConversion &tsc_conversion,
19481ad6265SDimitry Andric const ContextSwitchRecord ¤t_record,
195*bdd1243dSDimitry Andric const std::optional<ContextSwitchRecord> &prev_record,
19681ad6265SDimitry Andric std::function<void(const ThreadContinuousExecution &execution)>
19781ad6265SDimitry Andric on_new_execution) {
19881ad6265SDimitry Andric if (!prev_record) {
19981ad6265SDimitry Andric if (current_record.IsOut()) {
20081ad6265SDimitry Andric on_new_execution(ThreadContinuousExecution::CreateOnlyEndExecution(
20181ad6265SDimitry Andric cpu_id, current_record.tid, current_record.pid, current_record.tsc));
20281ad6265SDimitry Andric }
20381ad6265SDimitry Andric // The 'in' case will be handled later when we try to look for its end
20481ad6265SDimitry Andric return Error::success();
20581ad6265SDimitry Andric }
20681ad6265SDimitry Andric
20781ad6265SDimitry Andric const ContextSwitchRecord &prev = *prev_record;
20881ad6265SDimitry Andric if (prev.tsc >= current_record.tsc)
20981ad6265SDimitry Andric return createStringError(
21081ad6265SDimitry Andric inconvertibleErrorCode(),
21181ad6265SDimitry Andric formatv("A context switch record doesn't happen after the previous "
21281ad6265SDimitry Andric "record. Previous TSC= {0}, current TSC = {1}.",
21381ad6265SDimitry Andric prev.tsc, current_record.tsc));
21481ad6265SDimitry Andric
21581ad6265SDimitry Andric if (current_record.IsIn() && prev.IsIn()) {
21681ad6265SDimitry Andric // We found two consecutive ins, which means that we didn't capture
21781ad6265SDimitry Andric // the end of the previous execution.
21881ad6265SDimitry Andric on_new_execution(ThreadContinuousExecution::CreateHintedEndExecution(
21981ad6265SDimitry Andric cpu_id, prev.tid, prev.pid, prev.tsc, current_record.tsc - 1));
22081ad6265SDimitry Andric } else if (current_record.IsOut() && prev.IsOut()) {
22181ad6265SDimitry Andric // We found two consecutive outs, that means that we didn't capture
22281ad6265SDimitry Andric // the beginning of the current execution.
22381ad6265SDimitry Andric on_new_execution(ThreadContinuousExecution::CreateHintedStartExecution(
22481ad6265SDimitry Andric cpu_id, current_record.tid, current_record.pid, prev.tsc + 1,
22581ad6265SDimitry Andric current_record.tsc));
22681ad6265SDimitry Andric } else if (current_record.IsOut() && prev.IsIn()) {
22781ad6265SDimitry Andric if (current_record.pid == prev.pid && current_record.tid == prev.tid) {
22881ad6265SDimitry Andric /// A complete execution
22981ad6265SDimitry Andric on_new_execution(ThreadContinuousExecution::CreateCompleteExecution(
23081ad6265SDimitry Andric cpu_id, current_record.tid, current_record.pid, prev.tsc,
23181ad6265SDimitry Andric current_record.tsc));
23281ad6265SDimitry Andric } else {
23381ad6265SDimitry Andric // An out after the in of a different thread. The first one doesn't
23481ad6265SDimitry Andric // have an end, and the second one doesn't have a start.
23581ad6265SDimitry Andric on_new_execution(ThreadContinuousExecution::CreateHintedEndExecution(
23681ad6265SDimitry Andric cpu_id, prev.tid, prev.pid, prev.tsc, current_record.tsc - 1));
23781ad6265SDimitry Andric on_new_execution(ThreadContinuousExecution::CreateHintedStartExecution(
23881ad6265SDimitry Andric cpu_id, current_record.tid, current_record.pid, prev.tsc + 1,
23981ad6265SDimitry Andric current_record.tsc));
24081ad6265SDimitry Andric }
24181ad6265SDimitry Andric }
24281ad6265SDimitry Andric return Error::success();
24381ad6265SDimitry Andric }
24481ad6265SDimitry Andric
24581ad6265SDimitry Andric Expected<std::vector<ThreadContinuousExecution>>
DecodePerfContextSwitchTrace(ArrayRef<uint8_t> data,cpu_id_t cpu_id,const LinuxPerfZeroTscConversion & tsc_conversion)24681ad6265SDimitry Andric lldb_private::trace_intel_pt::DecodePerfContextSwitchTrace(
24781ad6265SDimitry Andric ArrayRef<uint8_t> data, cpu_id_t cpu_id,
24881ad6265SDimitry Andric const LinuxPerfZeroTscConversion &tsc_conversion) {
24981ad6265SDimitry Andric
25081ad6265SDimitry Andric std::vector<ThreadContinuousExecution> executions;
25181ad6265SDimitry Andric
25281ad6265SDimitry Andric // This offset is used to create the error message in case of failures.
25381ad6265SDimitry Andric size_t offset = 0;
25481ad6265SDimitry Andric
25581ad6265SDimitry Andric auto do_decode = [&]() -> Error {
256*bdd1243dSDimitry Andric std::optional<ContextSwitchRecord> prev_record;
25781ad6265SDimitry Andric while (offset < data.size()) {
25881ad6265SDimitry Andric const perf_event_header &perf_record =
25981ad6265SDimitry Andric *reinterpret_cast<const perf_event_header *>(data.data() + offset);
26081ad6265SDimitry Andric if (Error err = perf_record.SanityCheck())
26181ad6265SDimitry Andric return err;
26281ad6265SDimitry Andric
26381ad6265SDimitry Andric if (perf_record.IsContextSwitchRecord()) {
26481ad6265SDimitry Andric const PerfContextSwitchRecord &context_switch_record =
26581ad6265SDimitry Andric *reinterpret_cast<const PerfContextSwitchRecord *>(data.data() +
26681ad6265SDimitry Andric offset);
26781ad6265SDimitry Andric ContextSwitchRecord record{
26881ad6265SDimitry Andric tsc_conversion.ToTSC(context_switch_record.time_in_nanos),
26981ad6265SDimitry Andric context_switch_record.IsOut(),
27081ad6265SDimitry Andric static_cast<lldb::pid_t>(context_switch_record.pid),
27181ad6265SDimitry Andric static_cast<lldb::tid_t>(context_switch_record.tid)};
27281ad6265SDimitry Andric
27381ad6265SDimitry Andric if (Error err = RecoverExecutionsFromConsecutiveRecords(
27481ad6265SDimitry Andric cpu_id, tsc_conversion, record, prev_record,
27581ad6265SDimitry Andric [&](const ThreadContinuousExecution &execution) {
27681ad6265SDimitry Andric executions.push_back(execution);
27781ad6265SDimitry Andric }))
27881ad6265SDimitry Andric return err;
27981ad6265SDimitry Andric
28081ad6265SDimitry Andric prev_record = record;
28181ad6265SDimitry Andric }
28281ad6265SDimitry Andric offset += perf_record.size;
28381ad6265SDimitry Andric }
28481ad6265SDimitry Andric
28581ad6265SDimitry Andric // We might have an incomplete last record
28681ad6265SDimitry Andric if (prev_record && prev_record->IsIn())
28781ad6265SDimitry Andric executions.push_back(ThreadContinuousExecution::CreateOnlyStartExecution(
28881ad6265SDimitry Andric cpu_id, prev_record->tid, prev_record->pid, prev_record->tsc));
28981ad6265SDimitry Andric return Error::success();
29081ad6265SDimitry Andric };
29181ad6265SDimitry Andric
29281ad6265SDimitry Andric if (Error err = do_decode())
29381ad6265SDimitry Andric return createStringError(inconvertibleErrorCode(),
29481ad6265SDimitry Andric formatv("Malformed perf context switch trace for "
29581ad6265SDimitry Andric "cpu {0} at offset {1}. {2}",
29681ad6265SDimitry Andric cpu_id, offset, toString(std::move(err))));
29781ad6265SDimitry Andric
29881ad6265SDimitry Andric return executions;
29981ad6265SDimitry Andric }
300753f127fSDimitry Andric
301753f127fSDimitry Andric Expected<std::vector<uint8_t>>
FilterProcessesFromContextSwitchTrace(llvm::ArrayRef<uint8_t> data,const std::set<lldb::pid_t> & pids)302753f127fSDimitry Andric lldb_private::trace_intel_pt::FilterProcessesFromContextSwitchTrace(
303753f127fSDimitry Andric llvm::ArrayRef<uint8_t> data, const std::set<lldb::pid_t> &pids) {
304753f127fSDimitry Andric size_t offset = 0;
305753f127fSDimitry Andric std::vector<uint8_t> out_data;
306753f127fSDimitry Andric
307753f127fSDimitry Andric while (offset < data.size()) {
308753f127fSDimitry Andric const perf_event_header &perf_record =
309753f127fSDimitry Andric *reinterpret_cast<const perf_event_header *>(data.data() + offset);
310753f127fSDimitry Andric if (Error err = perf_record.SanityCheck())
311753f127fSDimitry Andric return std::move(err);
312753f127fSDimitry Andric bool should_copy = false;
313753f127fSDimitry Andric if (perf_record.IsContextSwitchRecord()) {
314753f127fSDimitry Andric const PerfContextSwitchRecord &context_switch_record =
315753f127fSDimitry Andric *reinterpret_cast<const PerfContextSwitchRecord *>(data.data() +
316753f127fSDimitry Andric offset);
317753f127fSDimitry Andric if (pids.count(context_switch_record.pid))
318753f127fSDimitry Andric should_copy = true;
319753f127fSDimitry Andric } else if (perf_record.IsErrorRecord()) {
320753f127fSDimitry Andric should_copy = true;
321753f127fSDimitry Andric }
322753f127fSDimitry Andric
323753f127fSDimitry Andric if (should_copy) {
324753f127fSDimitry Andric for (size_t i = 0; i < perf_record.size; i++) {
325753f127fSDimitry Andric out_data.push_back(data[offset + i]);
326753f127fSDimitry Andric }
327753f127fSDimitry Andric }
328753f127fSDimitry Andric
329753f127fSDimitry Andric offset += perf_record.size;
330753f127fSDimitry Andric }
331753f127fSDimitry Andric return out_data;
332753f127fSDimitry Andric }
333