/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "src/trace_processor/types/task_state.h"

#include <string.h>

namespace perfetto {
namespace trace_processor {
namespace ftrace_utils {

// static
TaskState TaskState::FromRawPrevState(
    uint16_t raw_state,
    base::Optional<VersionNumber> kernel_version) {
  return TaskState(raw_state, kernel_version);
}

// static
TaskState TaskState::FromSystrace(const char* state_str) {
  return TaskState(state_str);
}

// static
TaskState TaskState::FromParsedFlags(uint16_t parsed_state) {
  TaskState ret;
  ret.parsed_ = parsed_state;
  return ret;
}

// See header for extra details.
//
// Note to maintainers: going forward, the most likely "breaking" changes are:
// * a new flag is added to TASK_REPORT (see include/linux/sched.h kernel src)
// * a new report-specific flag is added above TASK_REPORT
// In both cases, this will change the value of TASK_REPORT_MAX that is used to
// report preemption is sched_switch. We'll need to modify this class to keep
// up, or make traced_probes record the sched_switch format string in traces.
//
// Note to maintainers: if changing the default kernel assumption or the 4.4
// codepath, you'll need to update ToRawStateOnlyForSystraceConversions().
TaskState::TaskState(uint16_t raw_state,
                     base::Optional<VersionNumber> opt_version) {
  // Values up to and including 0x20 (EXIT_ZOMBIE) never changed, so map them
  // directly onto ParsedFlag (we use the same flag bits for convenience).
  parsed_ = raw_state & (0x40 - 1);

  // Parsing upper bits depends on kernel version. Default to 4.4 because old
  // perfetto traces don't record kernel version.
  auto version = VersionNumber{4, 4};
  if (opt_version) {
    version = opt_version.value();
  }

  // Kernels 4.14+: flags up to and including 0x40 (TASK_PARKED) are reported
  // with their scheduler values. Whereas flags 0x80 (normally TASK_DEAD) and
  // above are masked off and repurposed for reporting-specific values.
  if (version >= VersionNumber{4, 14}) {
    if (raw_state & 0x40)  // TASK_PARKED
      parsed_ |= kParked;

    // REPORT_TASK_IDLE (0x80), which reports the TASK_IDLE composite state
    // (TASK_UNINTERRUPTIBLE | TASK_NOLOAD):
    if (raw_state & 0x80) {
      parsed_ |= kIdle;
    }

    // REPORT_TASK_MAX that sched_switch uses to report preemption. At the time
    // of writing 0x100 because REPORT_TASK_IDLE is the only report-specific
    // flag:
    if (raw_state & 0x100)
      parsed_ |= kPreempted;

    // Attempt to notice REPORT_TASK_MAX changing. If this dcheck fires, please
    // file a bug report against perfetto.
    if (raw_state & 0x200) {
      parsed_ = kInvalid;
      PERFETTO_DCHECK(false);
    }
    return;
  }

  // Before 4.14, sched_switch reported the full set of scheduler flags
  // (without masking down to TASK_REPORT). Note: several flags starting at
  // 0x40 have a different value to the above because 4.14 reordered them.
  // See https://github.com/torvalds/linux/commit/8ef9925b02.
  if (raw_state & 0x40)  // TASK_DEAD
    parsed_ |= kTaskDead;
  if (raw_state & 0x80)  // TASK_WAKEKILL
    parsed_ |= kWakeKill;
  if (raw_state & 0x100)  // TASK_WAKING
    parsed_ |= kWaking;
  if (raw_state & 0x200)  // TASK_PARKED
    parsed_ |= kParked;
  if (raw_state & 0x400)  // TASK_NOLOAD
    parsed_ |= kNoLoad;

  // Note: we do not bother with converting kUninterruptibleSleep+kNoLoad into
  // kIdle in this codepath for simplicity. Users that really care about
  // kthread idle state on older kernels will need to know that "DN" is a
  // synonym.

  // Kernel version range [4.8, 4.14) has TASK_NEW, hence preemption
  // (TASK_STATE_MAX) is 0x1000. We don't decode TASK_NEW itself since it will
  // never show up in sched_switch.
  if (version >= VersionNumber{4, 8}) {
    if (raw_state & 0x1000)
      parsed_ |= kPreempted;
  } else {
    // Kernel (..., 4.8), preemption (TASK_STATE_MAX) is 0x800. Assume all
    // kernels in this range have the 4.4 state of the bitmask. This is most
    // likely incorrect on <4.2 as that's when TASK_NOLOAD was introduced
    // (which means preemption is reported at a different bit).
    if (raw_state & 0x800)
      parsed_ |= kPreempted;
  }
}

TaskState::TaskStateStr TaskState::ToString(char separator) const {
  if (!is_valid()) {
    return TaskStateStr{"?"};
  }

  // Character aliases follow sched_switch's format string.
  char buffer[32];
  size_t pos = 0;
  if (is_runnable()) {
    buffer[pos++] = 'R';
    if (parsed_ & kPreempted) {
      buffer[pos++] = '+';
      PERFETTO_DCHECK(parsed_ == kPreempted);
    }
  } else {
    auto append = [&](ParsedFlag flag, char c) {
      if (!(parsed_ & flag))
        return;
      if (separator && pos != 0)
        buffer[pos++] = separator;
      buffer[pos++] = c;
    };
    append(kInterruptibleSleep, 'S');
    append(kUninterruptibleSleep, 'D');  // (D)isk sleep
    append(kStopped, 'T');
    append(kTraced, 't');
    append(kExitDead, 'X');
    append(kExitZombie, 'Z');
    append(kParked, 'P');
    append(kTaskDead, 'x');
    append(kWakeKill, 'K');
    append(kWaking, 'W');
    append(kNoLoad, 'N');
    append(kIdle, 'I');
  }

  TaskStateStr output{};
  size_t sz = (pos < output.size() - 1) ? pos : output.size() - 1;
  memcpy(output.data(), buffer, sz);
  return output;
}

// Used when parsing systrace, i.e. textual ftrace output.
TaskState::TaskState(const char* state_str) {
  parsed_ = 0;
  if (!state_str || state_str[0] == '\0') {
    parsed_ = kInvalid;
    return;
  }

  // R or R+, otherwise invalid
  if (state_str[0] == 'R') {
    parsed_ = kRunnable;
    if (!strncmp(state_str, "R+", 3))
      parsed_ |= kPreempted;
    return;
  }

  for (size_t i = 0; state_str[i] != '\0'; i++) {
    char c = state_str[i];
    if (c == 'R' || c == '+') {
      parsed_ = kInvalid;
      return;
    }
    if (c == '|')
      continue;

    auto parse = [&](ParsedFlag flag, char symbol) {
      if (c == symbol)
        parsed_ |= flag;
      return c == symbol;
    };
    bool recognized = false;
    recognized |= parse(kInterruptibleSleep, 'S');
    recognized |= parse(kUninterruptibleSleep, 'D');  // (D)isk sleep
    recognized |= parse(kStopped, 'T');
    recognized |= parse(kTraced, 't');
    recognized |= parse(kExitDead, 'X');
    recognized |= parse(kExitZombie, 'Z');
    recognized |= parse(kParked, 'P');
    recognized |= parse(kTaskDead, 'x');
    recognized |= parse(kWakeKill, 'K');
    recognized |= parse(kWaking, 'W');
    recognized |= parse(kNoLoad, 'N');
    recognized |= parse(kIdle, 'I');
    if (!recognized) {
      parsed_ = kInvalid;
      return;
    }
  }
}

// Hard-assume 4.4 flag layout per the header rationale.
uint16_t TaskState::ToRawStateOnlyForSystraceConversions() const {
  if (parsed_ == kInvalid)
    return 0xffff;

  if (parsed_ == kPreempted)
    return 0x0800;

  uint16_t ret = parsed_ & (0x40 - 1);
  if (parsed_ & kTaskDead)
    ret |= 0x40;
  if (parsed_ & kWakeKill)
    ret |= 0x80;
  if (parsed_ & kWaking)
    ret |= 0x100;
  if (parsed_ & kParked)
    ret |= 0x200;
  if (parsed_ & kNoLoad)
    ret |= 0x400;

  // Expand kIdle into the underlying kUninterruptibleSleep + kNoLoad.
  if (parsed_ & kIdle)
    ret |= (0x2 | 0x400);

  return ret;
}

}  // namespace ftrace_utils
}  // namespace trace_processor
}  // namespace perfetto
