<commit_before>/*
 * Copyright 2019 Google Inc.
 *
 * 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 "paragraph_skia.h"

#include <algorithm>

namespace txt {

namespace skt = skia::textlayout;

namespace {

// Convert SkFontStyle::Weight values (ranging from 100-900) to txt::FontWeight
// values (ranging from 0-8).
txt::FontWeight GetTxtFontWeight(int font_weight) {
  int txt_weight = (font_weight - 100) / 100;
  txt_weight = std::clamp(txt_weight, static_cast<int>(txt::FontWeight::w100),
                          static_cast<int>(txt::FontWeight::w900));
  return static_cast<txt::FontWeight>(txt_weight);
}

txt::FontStyle GetTxtFontStyle(SkFontStyle::Slant font_slant) {
  return font_slant == SkFontStyle::Slant::kUpright_Slant
             ? txt::FontStyle::normal
             : txt::FontStyle::italic;
}

TextStyle SkiaToTxt(const skt::TextStyle& skia) {
  TextStyle txt;

  txt.color = skia.getColor();
  txt.decoration = static_cast<TextDecoration>(skia.getDecorationType());
  txt.decoration_color = skia.getDecorationColor();
  txt.decoration_style =
      static_cast<TextDecorationStyle>(skia.getDecorationStyle());
  txt.decoration_thickness_multiplier =
      SkScalarToDouble(skia.getDecorationThicknessMultiplier());
  txt.font_weight = GetTxtFontWeight(skia.getFontStyle().weight());
  txt.font_style = GetTxtFontStyle(skia.getFontStyle().slant());

  txt.text_baseline = static_cast<TextBaseline>(skia.getTextBaseline());

  for (const SkString& font_family : skia.getFontFamilies()) {
    txt.font_families.emplace_back(font_family.c_str());
  }

  txt.font_size = SkScalarToDouble(skia.getFontSize());
  txt.letter_spacing = SkScalarToDouble(skia.getLetterSpacing());
  txt.word_spacing = SkScalarToDouble(skia.getWordSpacing());
  txt.height = SkScalarToDouble(skia.getHeight());

  txt.locale = skia.getLocale().c_str();
  if (skia.hasBackground()) {
    txt.background = skia.getBackground();
  }
  if (skia.hasForeground()) {
    txt.foreground = skia.getForeground();
  }

  txt.text_shadows.clear();
  for (const skt::TextShadow& skia_shadow : skia.getShadows()) {
    txt::TextShadow shadow;
    shadow.offset = skia_shadow.fOffset;
    shadow.blur_radius = skia_shadow.fBlurRadius;
    shadow.color = skia_shadow.fColor;
    txt.text_shadows.emplace_back(shadow);
  }

  return txt;
}

}  // anonymous namespace

ParagraphSkia::ParagraphSkia(std::unique_ptr<skt::Paragraph> paragraph)
    : paragraph_(std::move(paragraph)) {}

double ParagraphSkia::GetMaxWidth() {
  return SkScalarToDouble(paragraph_->getMaxWidth());
}

double ParagraphSkia::GetHeight() {
  return SkScalarToDouble(paragraph_->getHeight());
}

double ParagraphSkia::GetLongestLine() {
  return SkScalarToDouble(paragraph_->getLongestLine());
}

std::vector<LineMetrics>& ParagraphSkia::GetLineMetrics() {
  if (!line_metrics_) {
    std::vector<skt::LineMetrics> metrics;
    paragraph_->getLineMetrics(metrics);

    line_metrics_.emplace();
    for (const skt::LineMetrics& skm : metrics) {
      LineMetrics& txtm = line_metrics_->emplace_back(
          skm.fStartIndex, skm.fEndIndex, skm.fEndExcludingWhitespaces,
          skm.fEndIncludingNewline, skm.fHardBreak);
      txtm.ascent = skm.fAscent;
      txtm.descent = skm.fDescent;
      txtm.unscaled_ascent = skm.fUnscaledAscent;
      txtm.height = skm.fHeight;
      txtm.width = skm.fWidth;
      txtm.left = skm.fLeft;
      txtm.baseline = skm.fBaseline;
      txtm.line_number = skm.fLineNumber;

      for (const auto& sk_iter : skm.fLineMetrics) {
        const skt::StyleMetrics& sk_style_metrics = sk_iter.second;
        line_metrics_styles_.push_back(SkiaToTxt(*sk_style_metrics.text_style));
        txtm.run_metrics.emplace(
            std::piecewise_construct, std::forward_as_tuple(sk_iter.first),
            std::forward_as_tuple(&line_metrics_styles_.back(),
                                  sk_style_metrics.font_metrics));
      }
    }
  }

  return line_metrics_.value();
}

double ParagraphSkia::GetMinIntrinsicWidth() {
  return SkScalarToDouble(paragraph_->getMinIntrinsicWidth());
}

double ParagraphSkia::GetMaxIntrinsicWidth() {
  return SkScalarToDouble(paragraph_->getMaxIntrinsicWidth());
}

double ParagraphSkia::GetAlphabeticBaseline() {
  return SkScalarToDouble(paragraph_->getAlphabeticBaseline());
}

double ParagraphSkia::GetIdeographicBaseline() {
  return SkScalarToDouble(paragraph_->getIdeographicBaseline());
}

bool ParagraphSkia::DidExceedMaxLines() {
  return paragraph_->didExceedMaxLines();
}

void ParagraphSkia::Layout(double width) {
  paragraph_->layout(width);
}

void ParagraphSkia::Paint(SkCanvas* canvas, double x, double y) {
  paragraph_->paint(canvas, x, y);
}

std::vector<Paragraph::TextBox> ParagraphSkia::GetRectsForRange(
    size_t start,
    size_t end,
    RectHeightStyle rect_height_style,
    RectWidthStyle rect_width_style) {
  std::vector<skt::TextBox> skia_boxes = paragraph_->getRectsForRange(
      start, end, static_cast<skt::RectHeightStyle>(rect_height_style),
      static_cast<skt::RectWidthStyle>(rect_width_style));

  std::vector<Paragraph::TextBox> boxes;
  for (const skt::TextBox skia_box : skia_boxes) {
    boxes.emplace_back(skia_box.rect,
                       static_cast<TextDirection>(skia_box.direction));
  }

  return boxes;
}

std::vector<Paragraph::TextBox> ParagraphSkia::GetRectsForPlaceholders() {
  std::vector<skt::TextBox> skia_boxes = paragraph_->getRectsForPlaceholders();

  std::vector<Paragraph::TextBox> boxes;
  for (const skt::TextBox skia_box : skia_boxes) {
    boxes.emplace_back(skia_box.rect,
                       static_cast<TextDirection>(skia_box.direction));
  }

  return boxes;
}

Paragraph::PositionWithAffinity ParagraphSkia::GetGlyphPositionAtCoordinate(
    double dx,
    double dy) {
  skt::PositionWithAffinity skia_pos =
      paragraph_->getGlyphPositionAtCoordinate(dx, dy);

  return ParagraphSkia::PositionWithAffinity(
      skia_pos.position, static_cast<Affinity>(skia_pos.affinity));
}

Paragraph::Range<size_t> ParagraphSkia::GetWordBoundary(size_t offset) {
  skt::SkRange<size_t> range = paragraph_->getWordBoundary(offset);
  return Paragraph::Range<size_t>(range.start, range.end);
}

}  // namespace txt
<commit_msg>Use references when iterating over SkParagraph text boxes (#23837)<commit_after>/*
 * Copyright 2019 Google Inc.
 *
 * 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 "paragraph_skia.h"

#include <algorithm>

namespace txt {

namespace skt = skia::textlayout;

namespace {

// Convert SkFontStyle::Weight values (ranging from 100-900) to txt::FontWeight
// values (ranging from 0-8).
txt::FontWeight GetTxtFontWeight(int font_weight) {
  int txt_weight = (font_weight - 100) / 100;
  txt_weight = std::clamp(txt_weight, static_cast<int>(txt::FontWeight::w100),
                          static_cast<int>(txt::FontWeight::w900));
  return static_cast<txt::FontWeight>(txt_weight);
}

txt::FontStyle GetTxtFontStyle(SkFontStyle::Slant font_slant) {
  return font_slant == SkFontStyle::Slant::kUpright_Slant
             ? txt::FontStyle::normal
             : txt::FontStyle::italic;
}

TextStyle SkiaToTxt(const skt::TextStyle& skia) {
  TextStyle txt;

  txt.color = skia.getColor();
  txt.decoration = static_cast<TextDecoration>(skia.getDecorationType());
  txt.decoration_color = skia.getDecorationColor();
  txt.decoration_style =
      static_cast<TextDecorationStyle>(skia.getDecorationStyle());
  txt.decoration_thickness_multiplier =
      SkScalarToDouble(skia.getDecorationThicknessMultiplier());
  txt.font_weight = GetTxtFontWeight(skia.getFontStyle().weight());
  txt.font_style = GetTxtFontStyle(skia.getFontStyle().slant());

  txt.text_baseline = static_cast<TextBaseline>(skia.getTextBaseline());

  for (const SkString& font_family : skia.getFontFamilies()) {
    txt.font_families.emplace_back(font_family.c_str());
  }

  txt.font_size = SkScalarToDouble(skia.getFontSize());
  txt.letter_spacing = SkScalarToDouble(skia.getLetterSpacing());
  txt.word_spacing = SkScalarToDouble(skia.getWordSpacing());
  txt.height = SkScalarToDouble(skia.getHeight());

  txt.locale = skia.getLocale().c_str();
  if (skia.hasBackground()) {
    txt.background = skia.getBackground();
  }
  if (skia.hasForeground()) {
    txt.foreground = skia.getForeground();
  }

  txt.text_shadows.clear();
  for (const skt::TextShadow& skia_shadow : skia.getShadows()) {
    txt::TextShadow shadow;
    shadow.offset = skia_shadow.fOffset;
    shadow.blur_radius = skia_shadow.fBlurRadius;
    shadow.color = skia_shadow.fColor;
    txt.text_shadows.emplace_back(shadow);
  }

  return txt;
}

}  // anonymous namespace

ParagraphSkia::ParagraphSkia(std::unique_ptr<skt::Paragraph> paragraph)
    : paragraph_(std::move(paragraph)) {}

double ParagraphSkia::GetMaxWidth() {
  return SkScalarToDouble(paragraph_->getMaxWidth());
}

double ParagraphSkia::GetHeight() {
  return SkScalarToDouble(paragraph_->getHeight());
}

double ParagraphSkia::GetLongestLine() {
  return SkScalarToDouble(paragraph_->getLongestLine());
}

std::vector<LineMetrics>& ParagraphSkia::GetLineMetrics() {
  if (!line_metrics_) {
    std::vector<skt::LineMetrics> metrics;
    paragraph_->getLineMetrics(metrics);

    line_metrics_.emplace();
    for (const skt::LineMetrics& skm : metrics) {
      LineMetrics& txtm = line_metrics_->emplace_back(
          skm.fStartIndex, skm.fEndIndex, skm.fEndExcludingWhitespaces,
          skm.fEndIncludingNewline, skm.fHardBreak);
      txtm.ascent = skm.fAscent;
      txtm.descent = skm.fDescent;
      txtm.unscaled_ascent = skm.fUnscaledAscent;
      txtm.height = skm.fHeight;
      txtm.width = skm.fWidth;
      txtm.left = skm.fLeft;
      txtm.baseline = skm.fBaseline;
      txtm.line_number = skm.fLineNumber;

      for (const auto& sk_iter : skm.fLineMetrics) {
        const skt::StyleMetrics& sk_style_metrics = sk_iter.second;
        line_metrics_styles_.push_back(SkiaToTxt(*sk_style_metrics.text_style));
        txtm.run_metrics.emplace(
            std::piecewise_construct, std::forward_as_tuple(sk_iter.first),
            std::forward_as_tuple(&line_metrics_styles_.back(),
                                  sk_style_metrics.font_metrics));
      }
    }
  }

  return line_metrics_.value();
}

double ParagraphSkia::GetMinIntrinsicWidth() {
  return SkScalarToDouble(paragraph_->getMinIntrinsicWidth());
}

double ParagraphSkia::GetMaxIntrinsicWidth() {
  return SkScalarToDouble(paragraph_->getMaxIntrinsicWidth());
}

double ParagraphSkia::GetAlphabeticBaseline() {
  return SkScalarToDouble(paragraph_->getAlphabeticBaseline());
}

double ParagraphSkia::GetIdeographicBaseline() {
  return SkScalarToDouble(paragraph_->getIdeographicBaseline());
}

bool ParagraphSkia::DidExceedMaxLines() {
  return paragraph_->didExceedMaxLines();
}

void ParagraphSkia::Layout(double width) {
  paragraph_->layout(width);
}

void ParagraphSkia::Paint(SkCanvas* canvas, double x, double y) {
  paragraph_->paint(canvas, x, y);
}

std::vector<Paragraph::TextBox> ParagraphSkia::GetRectsForRange(
    size_t start,
    size_t end,
    RectHeightStyle rect_height_style,
    RectWidthStyle rect_width_style) {
  std::vector<skt::TextBox> skia_boxes = paragraph_->getRectsForRange(
      start, end, static_cast<skt::RectHeightStyle>(rect_height_style),
      static_cast<skt::RectWidthStyle>(rect_width_style));

  std::vector<Paragraph::TextBox> boxes;
  for (const skt::TextBox& skia_box : skia_boxes) {
    boxes.emplace_back(skia_box.rect,
                       static_cast<TextDirection>(skia_box.direction));
  }

  return boxes;
}

std::vector<Paragraph::TextBox> ParagraphSkia::GetRectsForPlaceholders() {
  std::vector<skt::TextBox> skia_boxes = paragraph_->getRectsForPlaceholders();

  std::vector<Paragraph::TextBox> boxes;
  for (const skt::TextBox& skia_box : skia_boxes) {
    boxes.emplace_back(skia_box.rect,
                       static_cast<TextDirection>(skia_box.direction));
  }

  return boxes;
}

Paragraph::PositionWithAffinity ParagraphSkia::GetGlyphPositionAtCoordinate(
    double dx,
    double dy) {
  skt::PositionWithAffinity skia_pos =
      paragraph_->getGlyphPositionAtCoordinate(dx, dy);

  return ParagraphSkia::PositionWithAffinity(
      skia_pos.position, static_cast<Affinity>(skia_pos.affinity));
}

Paragraph::Range<size_t> ParagraphSkia::GetWordBoundary(size_t offset) {
  skt::SkRange<size_t> range = paragraph_->getWordBoundary(offset);
  return Paragraph::Range<size_t>(range.start, range.end);
}

}  // namespace txt
<|endoftext|>


<commit_before>// Allocate blocks to MPI processes.  Blocks can be split among
// multiple processes, and multiple blocks can be assigned to a single
// process.  The algorithm ensures that all of the group's of processes
// do not cross node boundaries.

// With multiple nodes, this turns into a bin-packing problem.  The
// major difference from the classical bin-packing problem is that we
// have a fixed set of bins.  The capacity of each node is also
// somewhat elastic, in that we can cram more blocks into a node if
// they will not fit anywhere else.

// This algorithm starts by assigning blocks to nodes, where the
// number of procs for a given block is
//
//   num_procs=min(block_cost/average_cost,1)
//
// rounded down.  The block is assigned to the node with the most
// remaining spots.  This is a Worst Fit First method, which is
// usually not the first choice for bin-packing.  We do this because
// this method usually overloads these multi-cpu blocks.  Putting them
// on separate nodes means that, if there are any left-over procs,
// they can be separately allocated to these high cost blocks.
//
// It can happen that it will not fit on any node, because each node
// only has a few procs left.  In that case, the block will get
// assigned to whatever node has the most leftover procs.
//
// This will usually overload some procs, but guarantees that all
// blocks will get allocated somewhere.  This usually results in some
// left over procs on nodes.  We assign these left over procs to the
// highest cost block_map, where the cost is the total cost of all
// blocks divided by by the number of procs assigned to that
// block_map.
//
// If the block_map has multiple blocks, then instead of assiging more
// procs, we create a new block_map.  The blocks are then split
// between the old and new block_maps.
//
// This algorithm is certainly far from optimal.
//
// 1) When block_maps are split, they are not rebalanced amongst all
// the block_maps.
//
// 2) When large blocks are forced to fit into a node, there is no
// sharing of procs between the existing block_maps and the new entry.

#include "Block_Cost.hxx"
#include "Block_Map.hxx"

#include <numeric>
#include <algorithm>
#include <stdexcept>

#include <iostream>

std::vector<std::vector<Block_Map>>
compute_block_grid_mapping(const size_t &procs_per_node,
                           const size_t &num_nodes,
                           const std::vector<Block_Cost> &block_costs)
{
  // We do computations in integers to make sure that the results are
  // the same on different processers.
  const size_t total_cost(
    std::accumulate(block_costs.begin(), block_costs.end(), size_t(0),
                    [](const size_t &cost, const Block_Cost &element) {
                      return cost + element.cost;
                    }));
  const size_t num_procs(procs_per_node * num_nodes);
  std::vector<size_t> available_procs(num_nodes, procs_per_node);

  std::vector<std::vector<Block_Map>> result(num_nodes);

  auto multi_proc_end(std::find_if(
    block_costs.begin(), block_costs.end(), [&](const Block_Cost &block) {
      return num_procs * block.cost <= total_cost;
    }));

  size_t remaining_cost(total_cost), remaining_procs(num_procs);
  for(auto block(block_costs.begin()); block != multi_proc_end; ++block)
    {
      // Always add block_map's to the node with the most available
      // procs.  This is Worst Fit First.

      size_t max_available_node(std::distance(
        available_procs.begin(),
        std::max_element(available_procs.begin(), available_procs.end())));

      size_t procs_for_block = std::min(
        available_procs[max_available_node],
        std::max(size_t(1), size_t(block->cost * num_procs / total_cost)));
      Block_Map block_map(procs_for_block, block->cost, {block->index});
      result[max_available_node].push_back(block_map);
      available_procs[max_available_node] -= procs_for_block;
      remaining_cost -= block->cost;
      remaining_procs -= procs_for_block;
    }

  if(remaining_procs != num_procs)
    {
      size_t required_procs((remaining_cost * num_procs + (total_cost - 1))
                            / total_cost);
      size_t extra_procs(remaining_procs - required_procs);
      while(extra_procs > 0)
        {
          std::vector<Block_Map>::iterator max_element(result.front().end());
          size_t max_node;
          for(size_t node = 0; node < result.size(); ++node)
            {
              if(available_procs[node] > 0 && !result[node].empty())
                {
                  auto max_element_on_node(std::max_element(
                    result[node].begin(), result[node].end()));
                  if(max_element == result.front().end()
                     || *max_element < *max_element_on_node)
                    {
                      max_element = max_element_on_node;
                      max_node = node;
                    }
                }
            }
          if(max_element != result.front().end())
            {
              max_element->num_procs += 1;
              --available_procs[max_node];
              --extra_procs;
            }
          else
            {
              break;
            }
        }
    }
  std::vector<std::vector<Block_Map>> available_block_maps(num_nodes);
  for(size_t node = 0; node < num_nodes; ++node)
    {
      available_block_maps[node].resize(available_procs[node]);
      for(auto &block : available_block_maps[node])
        {
          block.num_procs = 1;
        }
    }

  for(auto block(multi_proc_end); block != block_costs.end(); ++block)
    {
      size_t min_cost(std::numeric_limits<size_t>::max());
      auto min_block(available_block_maps.at(0).end());
      for(size_t node(0); node < num_nodes; ++node)
        {
          if(!available_block_maps[node].empty())
            {
              auto block = std::min_element(available_block_maps[node].begin(),
                                            available_block_maps[node].end());
              if(block->cost < min_cost)
                {
                  min_block = block;
                  min_cost = block->cost;
                }
            }
        }
      min_block->cost += block->cost;
      min_block->block_indices.push_back(block->index);
    }
  for(size_t node = 0; node < num_nodes; ++node)
    for(auto &available : available_block_maps[node])
      {
        result[node].push_back(available);
      }
  return result;
}
<commit_msg>Loudly fail if unable to find cores for a block<commit_after>// Allocate blocks to MPI processes.  Blocks can be split among
// multiple processes, and multiple blocks can be assigned to a single
// process.  The algorithm ensures that all of the group's of processes
// do not cross node boundaries.

// With multiple nodes, this turns into a bin-packing problem.  The
// major difference from the classical bin-packing problem is that we
// have a fixed set of bins.  The capacity of each node is also
// somewhat elastic, in that we can cram more blocks into a node if
// they will not fit anywhere else.

// This algorithm starts by assigning blocks to nodes, where the
// number of procs for a given block is
//
//   num_procs=min(block_cost/average_cost,1)
//
// rounded down.  The block is assigned to the node with the most
// remaining spots.  This is a Worst Fit First method, which is
// usually not the first choice for bin-packing.  We do this because
// this method usually overloads these multi-cpu blocks.  Putting them
// on separate nodes means that, if there are any left-over procs,
// they can be separately allocated to these high cost blocks.
//
// It can happen that it will not fit on any node, because each node
// only has a few procs left.  In that case, the block will get
// assigned to whatever node has the most leftover procs.
//
// This will usually overload some procs, but guarantees that all
// blocks will get allocated somewhere.  This usually results in some
// left over procs on nodes.  We assign these left over procs to the
// highest cost block_map, where the cost is the total cost of all
// blocks divided by by the number of procs assigned to that
// block_map.
//
// If the block_map has multiple blocks, then instead of assiging more
// procs, we create a new block_map.  The blocks are then split
// between the old and new block_maps.
//
// This algorithm is certainly far from optimal.
//
// 1) When block_maps are split, they are not rebalanced amongst all
// the block_maps.
//
// 2) When large blocks are forced to fit into a node, there is no
// sharing of procs between the existing block_maps and the new entry.

#include "Block_Cost.hxx"
#include "Block_Map.hxx"

#include <numeric>
#include <algorithm>
#include <stdexcept>

#include <iostream>

std::vector<std::vector<Block_Map>>
compute_block_grid_mapping(const size_t &procs_per_node,
                           const size_t &num_nodes,
                           const std::vector<Block_Cost> &block_costs)
{
  // We do computations in integers to make sure that the results are
  // the same on different processers.
  const size_t total_cost(
    std::accumulate(block_costs.begin(), block_costs.end(), size_t(0),
                    [](const size_t &cost, const Block_Cost &element) {
                      return cost + element.cost;
                    }));
  const size_t num_procs(procs_per_node * num_nodes);
  std::vector<size_t> available_procs(num_nodes, procs_per_node);

  std::vector<std::vector<Block_Map>> result(num_nodes);

  auto multi_proc_end(std::find_if(
    block_costs.begin(), block_costs.end(), [&](const Block_Cost &block) {
      return num_procs * block.cost <= total_cost;
    }));

  size_t remaining_cost(total_cost), remaining_procs(num_procs);
  for(auto block(block_costs.begin()); block != multi_proc_end; ++block)
    {
      // Always add block_map's to the node with the most available
      // procs.  This is Worst Fit First.

      size_t max_available_node(std::distance(
        available_procs.begin(),
        std::max_element(available_procs.begin(), available_procs.end())));

      size_t procs_for_block = std::min(
        available_procs[max_available_node],
        std::max(size_t(1), size_t(block->cost * num_procs / total_cost)));
      Block_Map block_map(procs_for_block, block->cost, {block->index});
      result[max_available_node].push_back(block_map);
      available_procs[max_available_node] -= procs_for_block;
      remaining_cost -= block->cost;
      remaining_procs -= procs_for_block;
    }

  if(remaining_procs != num_procs)
    {
      size_t required_procs((remaining_cost * num_procs + (total_cost - 1))
                            / total_cost);
      size_t extra_procs(remaining_procs - required_procs);
      while(extra_procs > 0)
        {
          std::vector<Block_Map>::iterator max_element(result.front().end());
          size_t max_node;
          for(size_t node = 0; node < result.size(); ++node)
            {
              if(available_procs[node] > 0 && !result[node].empty())
                {
                  auto max_element_on_node(std::max_element(
                    result[node].begin(), result[node].end()));
                  if(max_element == result.front().end()
                     || *max_element < *max_element_on_node)
                    {
                      max_element = max_element_on_node;
                      max_node = node;
                    }
                }
            }
          if(max_element != result.front().end())
            {
              max_element->num_procs += 1;
              --available_procs[max_node];
              --extra_procs;
            }
          else
            {
              break;
            }
        }
    }
  std::vector<std::vector<Block_Map>> available_block_maps(num_nodes);
  for(size_t node = 0; node < num_nodes; ++node)
    {
      available_block_maps[node].resize(available_procs[node]);
      for(auto &block : available_block_maps[node])
        {
          block.num_procs = 1;
        }
    }

  for(auto block(multi_proc_end); block != block_costs.end(); ++block)
    {
      size_t min_cost(std::numeric_limits<size_t>::max());
      auto min_block(available_block_maps.at(0).end());
      for(size_t node(0); node < num_nodes; ++node)
        {
          if(!available_block_maps[node].empty())
            {
              auto block = std::min_element(available_block_maps[node].begin(),
                                            available_block_maps[node].end());
              if(block->cost < min_cost)
                {
                  min_block = block;
                  min_cost = block->cost;
                }
            }
        }
      if(min_block==available_block_maps.at(0).end())
        {
          throw std::runtime_error("INTERNAL ERROR: Unable to find any "
                                   "free processors for remaining blocks");
        }
      min_block->cost += block->cost;
      min_block->block_indices.push_back(block->index);
    }
  for(size_t node = 0; node < num_nodes; ++node)
    for(auto &available : available_block_maps[node])
      {
        result[node].push_back(available);
      }
  return result;
}
<|endoftext|>


<commit_before>// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2021 Intel Corporation. All Rights Reserved.

#include "converter-csv.hpp"
#include <iomanip>
#include <algorithm>
#include <iostream>

using namespace rs2::tools::converter;

converter_csv::motion_pose_frame_record::motion_pose_frame_record(rs2_stream stream_type, int stream_index,
    unsigned long long frame_number,
    double frame_ts, double backend_ts, double arrival_time,
    double p1, double p2, double p3,
    double p4, double p5, double p6, double p7) :
    _stream_type(stream_type),
    _stream_idx(stream_index),
    _frame_number(frame_number),
    _frame_ts(frame_ts),
    _backend_ts(backend_ts),
    _arrival_time(arrival_time),
    _params({ p1, p2, p3, p4, p5, p6, p7 })
{};

std::string converter_csv::motion_pose_frame_record::to_string() const
{
    std::stringstream ss;
    ss << rs2_stream_to_string(_stream_type) << "," << _frame_number << ","
        << std::fixed << std::setprecision(3) << _frame_ts << "," << _backend_ts << "," << _arrival_time;

    // IMU and Pose frame hold the sample data in addition to the frame's header attributes
    size_t specific_attributes = 0;
    if (val_in_range(_stream_type, { RS2_STREAM_GYRO,RS2_STREAM_ACCEL }))
        specific_attributes = 3;
    if (val_in_range(_stream_type, { RS2_STREAM_POSE }))
        specific_attributes = 7;

    for (auto i = 0; i < specific_attributes; i++)
        ss << "," << _params[i];
    ss << std::endl;
    return ss.str().c_str();
}

converter_csv::converter_csv(const std::string& filePath, rs2_stream streamType)
    : _filePath(filePath)
    , _streamType(streamType)
    , _imu_pose_collection()
    , _sub_workers_joined(false)
    , _m()
    , _cv()
{
}

void converter_csv::convert_depth(rs2::depth_frame& depthframe)
{
    if (frames_map_get_and_set(depthframe.get_profile().stream_type(), depthframe.get_frame_number())) {
        return;
    }

    start_worker(
        [this, &depthframe] {

            std::stringstream filename;
            filename << _filePath
                << "_" << depthframe.get_profile().stream_name()
                << "_" << std::setprecision(14) << std::fixed << depthframe.get_timestamp()
                << ".csv";

            std::stringstream metadata_file;
            metadata_file << _filePath
                << "_" << depthframe.get_profile().stream_name()
                << "_metadata_" << std::setprecision(14) << std::fixed << depthframe.get_timestamp()
                << ".txt";

            std::string filenameS = filename.str();
            std::string metadataS = metadata_file.str();

            add_sub_worker(
                [filenameS, metadataS, depthframe] {
                    std::ofstream fs(filenameS, std::ios::trunc);

                    if (fs) {
                        for (int y = 0; y < depthframe.get_height(); y++) {
                            auto delim = "";

                            for (int x = 0; x < depthframe.get_width(); x++) {
                                fs << delim << depthframe.get_distance(x, y);
                                delim = ",";
                            }
                            fs << '\n';
                        }
                        fs.flush();
                    }
                    metadata_to_txtfile(depthframe, metadataS);
                });
            wait_sub_workers();
        });
}

std::string converter_csv::get_time_string() const
{
    struct tm newtime;
    time_t now = time(0);
    localtime_s(&newtime, &now);

    std::stringstream ss;
    ss << newtime.tm_hour;
    ss << newtime.tm_min;
    ss << newtime.tm_sec;
    return ss.str();
}

void converter_csv::output_to_csv()
{
    if (!_imu_pose_collection.size())
        throw std::runtime_error(stringify() << "No data collected, aborting");

    // Report amount of frames collected
    std::vector<uint64_t> frames_per_stream;
    for (const auto& kv : _imu_pose_collection)
        frames_per_stream.emplace_back(kv.second.size());

    std::sort(frames_per_stream.begin(), frames_per_stream.end());

    // Serialize and store data into csv-like format
    static auto time_Str = get_time_string();
    std::stringstream filename;
    filename << _filePath  << "_" << time_Str <<"_imu_pose.csv";
    std::ofstream csv(filename.str());
    if (!csv.is_open())
        throw std::runtime_error(stringify() << "Cannot open the requested output file " << _filePath << ", please check permissions");

    for (const auto& elem : _imu_pose_collection)
    {
        csv << "\n\nStream Type,F#,HW Timestamp (ms),Backend Timestamp(ms),Host Timestamp(ms)"
            << (val_in_range(elem.first.first, { RS2_STREAM_GYRO,RS2_STREAM_ACCEL }) ? ",3DOF_x,3DOF_y,3DOF_z" : "")
            << (val_in_range(elem.first.first, { RS2_STREAM_POSE }) ? ",t_x,t_y,t_z,r_x,r_y,r_z,r_w" : "")
            << std::endl;

        for (auto i = 0; i < elem.second.size(); i++)
            csv << elem.second[i].to_string();
    }

}

void converter_csv::convert_motion_pose(rs2::frame& f)
{
    if (frames_map_get_and_set(f.get_profile().stream_type(), f.get_frame_number())) {
        return;
    }

    start_worker(
        [this, f] {

            add_sub_worker(
                [this, f] {
                    auto stream_uid = std::make_pair(f.get_profile().stream_type(),
                        f.get_profile().stream_index());

                    double frame_timestamp = 0.;
                    if (f.supports_frame_metadata(RS2_FRAME_METADATA_FRAME_TIMESTAMP))
                        frame_timestamp = f.get_frame_metadata(RS2_FRAME_METADATA_FRAME_TIMESTAMP);

                    double backend_timestamp = 0.;
                    if (f.supports_frame_metadata(RS2_FRAME_METADATA_BACKEND_TIMESTAMP))
                        backend_timestamp = f.get_frame_metadata(RS2_FRAME_METADATA_BACKEND_TIMESTAMP);

                    double time_of_arrival = 0.;
                    if (f.supports_frame_metadata(RS2_FRAME_METADATA_TIME_OF_ARRIVAL))
                        time_of_arrival = f.get_frame_metadata(RS2_FRAME_METADATA_TIME_OF_ARRIVAL);

                    motion_pose_frame_record record{ f.get_profile().stream_type(),
                                                f.get_profile().stream_index(),
                                                f.get_frame_number(),
                                                frame_timestamp,
                                                backend_timestamp,
                                                time_of_arrival};

                    if (auto motion = f.as<rs2::motion_frame>())
                    {
                        auto axes = motion.get_motion_data();
                        record._params = { axes.x, axes.y, axes.z };
                    }

                    if (auto pf = f.as<rs2::pose_frame>())
                    {
                        auto pose = pf.get_pose_data();
                        record._params = { pose.translation.x, pose.translation.y, pose.translation.z,
                                pose.rotation.x,pose.rotation.y,pose.rotation.z,pose.rotation.w };
                    }

                    _imu_pose_collection[stream_uid].emplace_back(record);
                });
            wait_sub_workers();
            _sub_workers_joined = true;
            _cv.notify_all();
        });
    std::unique_lock<std::mutex> lck(_m);
    while (!_sub_workers_joined)
        _cv.wait(lck);
    output_to_csv();
}

void converter_csv::convert(rs2::frame& frame)
{
    if (!(_streamType == rs2_stream::RS2_STREAM_ANY || frame.get_profile().stream_type() == _streamType))
        return;

    auto depthframe = frame.as<rs2::depth_frame>();
    if (depthframe)
    {
        convert_depth(depthframe);
        return;
    }

    auto motionframe = frame.as<rs2::motion_frame>();
    auto poseframe = frame.as<rs2::pose_frame>();
    if (motionframe || poseframe)
    {
        convert_motion_pose(frame);
        return;
    }
}
<commit_msg>using localtime instead of localtime_s<commit_after>// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2021 Intel Corporation. All Rights Reserved.

#include "converter-csv.hpp"
#include <iomanip>
#include <algorithm>
#include <iostream>

using namespace rs2::tools::converter;

converter_csv::motion_pose_frame_record::motion_pose_frame_record(rs2_stream stream_type, int stream_index,
    unsigned long long frame_number,
    double frame_ts, double backend_ts, double arrival_time,
    double p1, double p2, double p3,
    double p4, double p5, double p6, double p7) :
    _stream_type(stream_type),
    _stream_idx(stream_index),
    _frame_number(frame_number),
    _frame_ts(frame_ts),
    _backend_ts(backend_ts),
    _arrival_time(arrival_time),
    _params({ p1, p2, p3, p4, p5, p6, p7 }){}

std::string converter_csv::motion_pose_frame_record::to_string() const
{
    std::stringstream ss;
    ss << rs2_stream_to_string(_stream_type) << "," << _frame_number << ","
        << std::fixed << std::setprecision(3) << _frame_ts << "," << _backend_ts << "," << _arrival_time;

    // IMU and Pose frame hold the sample data in addition to the frame's header attributes
    size_t specific_attributes = 0;
    if (val_in_range(_stream_type, { RS2_STREAM_GYRO,RS2_STREAM_ACCEL }))
        specific_attributes = 3;
    if (val_in_range(_stream_type, { RS2_STREAM_POSE }))
        specific_attributes = 7;

    for (auto i = 0; i < specific_attributes; i++)
        ss << "," << _params[i];
    ss << std::endl;
    return ss.str().c_str();
}

converter_csv::converter_csv(const std::string& filePath, rs2_stream streamType)
    : _filePath(filePath)
    , _streamType(streamType)
    , _imu_pose_collection()
    , _sub_workers_joined(false)
    , _m()
    , _cv()
{
}

void converter_csv::convert_depth(rs2::depth_frame& depthframe)
{
    if (frames_map_get_and_set(depthframe.get_profile().stream_type(), depthframe.get_frame_number())) {
        return;
    }

    start_worker(
        [this, &depthframe] {

            std::stringstream filename;
            filename << _filePath
                << "_" << depthframe.get_profile().stream_name()
                << "_" << std::setprecision(14) << std::fixed << depthframe.get_timestamp()
                << ".csv";

            std::stringstream metadata_file;
            metadata_file << _filePath
                << "_" << depthframe.get_profile().stream_name()
                << "_metadata_" << std::setprecision(14) << std::fixed << depthframe.get_timestamp()
                << ".txt";

            std::string filenameS = filename.str();
            std::string metadataS = metadata_file.str();

            add_sub_worker(
                [filenameS, metadataS, depthframe] {
                    std::ofstream fs(filenameS, std::ios::trunc);

                    if (fs) {
                        for (int y = 0; y < depthframe.get_height(); y++) {
                            auto delim = "";

                            for (int x = 0; x < depthframe.get_width(); x++) {
                                fs << delim << depthframe.get_distance(x, y);
                                delim = ",";
                            }
                            fs << '\n';
                        }
                        fs.flush();
                    }
                    metadata_to_txtfile(depthframe, metadataS);
                });
            wait_sub_workers();
        });
}

std::string converter_csv::get_time_string() const
{
    /*struct tm newtime;
    time_t now = time(0);
    localtime_s(&newtime, &now);

    std::stringstream ss;
    ss << newtime.tm_hour;
    ss << newtime.tm_min;
    ss << newtime.tm_sec;
    return ss.str();*/
    auto t = time(nullptr);
    char buffer[20] = {};
    const tm* time = localtime(&t);
    if (nullptr != time)
        strftime(buffer, sizeof(buffer), "%Y%m%d%H%M%S", time);

    return std::string(buffer);
}

void converter_csv::output_to_csv()
{
    if (!_imu_pose_collection.size())
        throw std::runtime_error(stringify() << "No data collected, aborting");

    // Report amount of frames collected
    std::vector<uint64_t> frames_per_stream;
    for (const auto& kv : _imu_pose_collection)
        frames_per_stream.emplace_back(kv.second.size());

    std::sort(frames_per_stream.begin(), frames_per_stream.end());

    // Serialize and store data into csv-like format
    static auto time_Str = get_time_string();
    std::stringstream filename;
    filename << _filePath  << "_" << time_Str <<"_imu_pose.csv";
    std::ofstream csv(filename.str());
    if (!csv.is_open())
        throw std::runtime_error(stringify() << "Cannot open the requested output file " << _filePath << ", please check permissions");

    for (const auto& elem : _imu_pose_collection)
    {
        csv << "\n\nStream Type,F#,HW Timestamp (ms),Backend Timestamp(ms),Host Timestamp(ms)"
            << (val_in_range(elem.first.first, { RS2_STREAM_GYRO,RS2_STREAM_ACCEL }) ? ",3DOF_x,3DOF_y,3DOF_z" : "")
            << (val_in_range(elem.first.first, { RS2_STREAM_POSE }) ? ",t_x,t_y,t_z,r_x,r_y,r_z,r_w" : "")
            << std::endl;

        for (auto i = 0; i < elem.second.size(); i++)
            csv << elem.second[i].to_string();
    }

}

void converter_csv::convert_motion_pose(rs2::frame& f)
{
    if (frames_map_get_and_set(f.get_profile().stream_type(), f.get_frame_number())) {
        return;
    }

    start_worker(
        [this, f] {

            add_sub_worker(
                [this, f] {
                    auto stream_uid = std::make_pair(f.get_profile().stream_type(),
                        f.get_profile().stream_index());

                    double frame_timestamp = 0.;
                    if (f.supports_frame_metadata(RS2_FRAME_METADATA_FRAME_TIMESTAMP))
                        frame_timestamp = f.get_frame_metadata(RS2_FRAME_METADATA_FRAME_TIMESTAMP);

                    double backend_timestamp = 0.;
                    if (f.supports_frame_metadata(RS2_FRAME_METADATA_BACKEND_TIMESTAMP))
                        backend_timestamp = f.get_frame_metadata(RS2_FRAME_METADATA_BACKEND_TIMESTAMP);

                    double time_of_arrival = 0.;
                    if (f.supports_frame_metadata(RS2_FRAME_METADATA_TIME_OF_ARRIVAL))
                        time_of_arrival = f.get_frame_metadata(RS2_FRAME_METADATA_TIME_OF_ARRIVAL);

                    motion_pose_frame_record record{ f.get_profile().stream_type(),
                                                f.get_profile().stream_index(),
                                                f.get_frame_number(),
                                                frame_timestamp,
                                                backend_timestamp,
                                                time_of_arrival};

                    if (auto motion = f.as<rs2::motion_frame>())
                    {
                        auto axes = motion.get_motion_data();
                        record._params = { axes.x, axes.y, axes.z };
                    }

                    if (auto pf = f.as<rs2::pose_frame>())
                    {
                        auto pose = pf.get_pose_data();
                        record._params = { pose.translation.x, pose.translation.y, pose.translation.z,
                                pose.rotation.x,pose.rotation.y,pose.rotation.z,pose.rotation.w };
                    }

                    _imu_pose_collection[stream_uid].emplace_back(record);
                });
            wait_sub_workers();
            _sub_workers_joined = true;
            _cv.notify_all();
        });
    std::unique_lock<std::mutex> lck(_m);
    while (!_sub_workers_joined)
        _cv.wait(lck);
    output_to_csv();
}

void converter_csv::convert(rs2::frame& frame)
{
    if (!(_streamType == rs2_stream::RS2_STREAM_ANY || frame.get_profile().stream_type() == _streamType))
        return;

    auto depthframe = frame.as<rs2::depth_frame>();
    if (depthframe)
    {
        convert_depth(depthframe);
        return;
    }

    auto motionframe = frame.as<rs2::motion_frame>();
    auto poseframe = frame.as<rs2::pose_frame>();
    if (motionframe || poseframe)
    {
        convert_motion_pose(frame);
        return;
    }
}
<|endoftext|>


<commit_before>/*ckwg +5
 * Copyright 2011 by Kitware, Inc. All Rights Reserved. Please refer to
 * KITWARE_LICENSE.TXT for licensing information, or contact General Counsel,
 * Kitware, Inc., 28 Corporate Drive, Clifton Park, NY 12065.
 */

#include <vistk/pipeline/config.h>
#include <vistk/pipeline/datum.h>
#include <vistk/pipeline/edge.h>
#include <vistk/pipeline/edge_exception.h>
#include <vistk/pipeline/modules.h>
#include <vistk/pipeline/process.h>
#include <vistk/pipeline/stamp.h>

#include <exception>
#include <iostream>
#include <string>

static void run_test(std::string const& test_name);

int
main(int argc, char* argv[])
{
  if (argc != 2)
  {
    std::cerr << "Error: Expected one argument" << std::endl;

    return 1;
  }

  std::string const test_name = argv[1];

  try
  {
    run_test(test_name);
  }
  catch (std::exception& e)
  {
    std::cerr << "Error: Unexpected exception: " << e.what() << std::endl;

    return 1;
  }

  return 0;
}

static void test_makes_dependency();
static void test_new_has_no_data();
static void test_new_is_not_full();
static void test_new_has_count_zero();
static void test_push_datum();
static void test_peek_datum();
static void test_pop_datum();
static void test_get_datum();
static void test_required_by_downstream();
static void test_null_upstream_process();
static void test_null_downstream_process();
static void test_set_upstream_process();
static void test_set_downstream_process();

void
run_test(std::string const& test_name)
{
  if (test_name == "makes_dependency")
  {
    test_makes_dependency();
  }
  else if (test_name == "new_has_no_data")
  {
    test_new_has_no_data();
  }
  else if (test_name == "new_is_not_full")
  {
    test_new_is_not_full();
  }
  else if (test_name == "new_has_count_zero")
  {
    test_new_has_count_zero();
  }
  else if (test_name == "push_datum")
  {
    test_push_datum();
  }
  else if (test_name == "peek_datum")
  {
    test_peek_datum();
  }
  else if (test_name == "pop_datum")
  {
    test_pop_datum();
  }
  else if (test_name == "get_datum")
  {
    test_get_datum();
  }
  else if (test_name == "required_by_downstream")
  {
    test_required_by_downstream();
  }
  else if (test_name == "null_upstream_process")
  {
    test_null_upstream_process();
  }
  else if (test_name == "null_downstream_process")
  {
    test_null_downstream_process();
  }
  else if (test_name == "set_upstream_process")
  {
    test_set_upstream_process();
  }
  else if (test_name == "set_downstream_process")
  {
    test_set_downstream_process();
  }
  else
  {
    std::cerr << "Error: Unknown test: " << test_name << std::endl;
  }
}

void
test_makes_dependency()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  edge->makes_dependency();
}

void
test_new_has_no_data()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  if (edge->has_data())
  {
    std::cerr << "Error: A new edge has data in it" << std::endl;
  }
}

void
test_new_is_not_full()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  if (edge->full_of_data())
  {
    std::cerr << "Error: A new edge is full of data" << std::endl;
  }
}

void
test_new_has_count_zero()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  if (edge->datum_count())
  {
    std::cerr << "Error: A new edge has a count" << std::endl;
  }
}

void
test_push_datum()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  vistk::datum_t const dat = vistk::datum::complete_datum();
  vistk::stamp_t const stamp = vistk::stamp::new_stamp();

  vistk::edge_datum_t const edat = vistk::edge_datum_t(dat, stamp);

  edge->push_datum(edat);

  if (edge->datum_count() != 1)
  {
    std::cerr << "Error: An edge with a pushed datum does not have a count of one" << std::endl;
  }

  edge->push_datum(edat);

  if (edge->datum_count() != 2)
  {
    std::cerr << "Error: An edge with two pushed data does not have a count of two" << std::endl;
  }
}

void
test_peek_datum()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  vistk::datum_t const dat = vistk::datum::complete_datum();
  vistk::stamp_t const stamp = vistk::stamp::new_stamp();

  vistk::edge_datum_t const edat = vistk::edge_datum_t(dat, stamp);

  edge->push_datum(edat);

  vistk::edge_datum_t const get_edat = edge->peek_datum();

  if (edge->datum_count() != 1)
  {
    std::cerr << "Error: An edge removed a datum on an peek" << std::endl;
  }

  if (*get_edat.get<1>() != *stamp)
  {
    std::cerr << "Error: The edge modified a stamp on a peek" << std::endl;
  }
}

void
test_pop_datum()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  vistk::datum_t const dat = vistk::datum::complete_datum();
  vistk::stamp_t const stamp = vistk::stamp::new_stamp();

  vistk::edge_datum_t const edat = vistk::edge_datum_t(dat, stamp);

  edge->push_datum(edat);

  edge->pop_datum();

  if (edge->datum_count())
  {
    std::cerr << "Error: An edge did not remove a datum on a pop" << std::endl;
  }
}

void
test_get_datum()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  vistk::datum_t const dat = vistk::datum::complete_datum();
  vistk::stamp_t const stamp = vistk::stamp::new_stamp();

  vistk::edge_datum_t const edat = vistk::edge_datum_t(dat, stamp);

  edge->push_datum(edat);

  vistk::edge_datum_t const get_edat = edge->get_datum();

  if (edge->datum_count() != 0)
  {
    std::cerr << "Error: An edge did not remove a datum on a get" << std::endl;
  }

  if (*get_edat.get<1>() != *stamp)
  {
    std::cerr << "Error: The edge modified a stamp on a get" << std::endl;
  }
}

void
test_required_by_downstream()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  if (!edge->required_by_downstream())
  {
    std::cerr << "Error: A new edge is not marked as required by downstream" << std::endl;
  }

  edge->set_required_by_downstream(false);

  if (edge->required_by_downstream())
  {
    std::cerr << "Error: Setting the requirement by downstream failed" << std::endl;
  }

  edge->set_required_by_downstream(true);

  if (!edge->required_by_downstream())
  {
    std::cerr << "Error: Setting the requirement by downstream failed" << std::endl;
  }
}

void
test_null_upstream_process()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  vistk::process_t const process;

  bool got_exception = false;

  try
  {
    edge->set_upstream_process(process);
  }
  catch (vistk::null_process_connection_exception& e)
  {
    got_exception = true;

    (void)e.what();
  }
  catch (std::exception& e)
  {
    std::cerr << "Error: Unexpected exception: "
              << e.what() << std::endl;

    got_exception = true;
  }

  if (!got_exception)
  {
    std::cerr << "Error: Did not get expected exception "
              << "when setting a NULL process as upstream" << std::endl;
  }
}

void
test_null_downstream_process()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  vistk::process_t const process;

  bool got_exception = false;

  try
  {
    edge->set_downstream_process(process);
  }
  catch (vistk::null_process_connection_exception& e)
  {
    got_exception = true;

    (void)e.what();
  }
  catch (std::exception& e)
  {
    std::cerr << "Error: Unexpected exception: "
              << e.what() << std::endl;

    got_exception = true;
  }

  if (!got_exception)
  {
    std::cerr << "Error: Did not get expected exception "
              << "when setting a NULL process as upstream" << std::endl;
  }
}

void
test_set_upstream_process()
{
  vistk::load_known_modules();

  vistk::process_registry_t const reg = vistk::process_registry::self();
  vistk::process_registry::type_t const proc_type = vistk::process_registry::type_t("numbers");

  vistk::config_t const proc_config = vistk::config::empty_config();

  vistk::process_t const process = reg->create_process(proc_type, proc_config);

  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  edge->set_upstream_process(process);

  bool got_exception = false;

  try
  {
    edge->set_upstream_process(process);
  }
  catch (vistk::input_already_connected_exception& e)
  {
    got_exception = true;

    (void)e.what();
  }
  catch (std::exception& e)
  {
    std::cerr << "Error: Unexpected exception: "
              << e.what() << std::endl;

    got_exception = true;
  }

  if (!got_exception)
  {
    std::cerr << "Error: Did not get expected exception "
              << "when setting a second process as upstream on an edge" << std::endl;
  }
}

void
test_set_downstream_process()
{
  vistk::load_known_modules();

  vistk::process_registry_t const reg = vistk::process_registry::self();
  vistk::process_registry::type_t const proc_type = vistk::process_registry::type_t("numbers");

  vistk::config_t const proc_config = vistk::config::empty_config();

  vistk::process_t const process = reg->create_process(proc_type, proc_config);

  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  edge->set_downstream_process(process);

  bool got_exception = false;

  try
  {
    edge->set_downstream_process(process);
  }
  catch (vistk::output_already_connected_exception& e)
  {
    got_exception = true;

    (void)e.what();
  }
  catch (std::exception& e)
  {
    std::cerr << "Error: Unexpected exception: "
              << e.what() << std::endl;

    got_exception = true;
  }

  if (!got_exception)
  {
    std::cerr << "Error: Did not get expected exception "
              << "when setting a second process as downstream on an edge" << std::endl;
  }
}
<commit_msg>Convert edge tests to use the new macro<commit_after>/*ckwg +5
 * Copyright 2011 by Kitware, Inc. All Rights Reserved. Please refer to
 * KITWARE_LICENSE.TXT for licensing information, or contact General Counsel,
 * Kitware, Inc., 28 Corporate Drive, Clifton Park, NY 12065.
 */

#include <test_common.h>

#include <vistk/pipeline/config.h>
#include <vistk/pipeline/datum.h>
#include <vistk/pipeline/edge.h>
#include <vistk/pipeline/edge_exception.h>
#include <vistk/pipeline/modules.h>
#include <vistk/pipeline/process.h>
#include <vistk/pipeline/stamp.h>

#include <exception>
#include <iostream>
#include <string>

static void run_test(std::string const& test_name);

int
main(int argc, char* argv[])
{
  if (argc != 2)
  {
    std::cerr << "Error: Expected one argument" << std::endl;

    return 1;
  }

  std::string const test_name = argv[1];

  try
  {
    run_test(test_name);
  }
  catch (std::exception& e)
  {
    std::cerr << "Error: Unexpected exception: " << e.what() << std::endl;

    return 1;
  }

  return 0;
}

static void test_makes_dependency();
static void test_new_has_no_data();
static void test_new_is_not_full();
static void test_new_has_count_zero();
static void test_push_datum();
static void test_peek_datum();
static void test_pop_datum();
static void test_get_datum();
static void test_required_by_downstream();
static void test_null_upstream_process();
static void test_null_downstream_process();
static void test_set_upstream_process();
static void test_set_downstream_process();

void
run_test(std::string const& test_name)
{
  if (test_name == "makes_dependency")
  {
    test_makes_dependency();
  }
  else if (test_name == "new_has_no_data")
  {
    test_new_has_no_data();
  }
  else if (test_name == "new_is_not_full")
  {
    test_new_is_not_full();
  }
  else if (test_name == "new_has_count_zero")
  {
    test_new_has_count_zero();
  }
  else if (test_name == "push_datum")
  {
    test_push_datum();
  }
  else if (test_name == "peek_datum")
  {
    test_peek_datum();
  }
  else if (test_name == "pop_datum")
  {
    test_pop_datum();
  }
  else if (test_name == "get_datum")
  {
    test_get_datum();
  }
  else if (test_name == "required_by_downstream")
  {
    test_required_by_downstream();
  }
  else if (test_name == "null_upstream_process")
  {
    test_null_upstream_process();
  }
  else if (test_name == "null_downstream_process")
  {
    test_null_downstream_process();
  }
  else if (test_name == "set_upstream_process")
  {
    test_set_upstream_process();
  }
  else if (test_name == "set_downstream_process")
  {
    test_set_downstream_process();
  }
  else
  {
    std::cerr << "Error: Unknown test: " << test_name << std::endl;
  }
}

void
test_makes_dependency()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  edge->makes_dependency();
}

void
test_new_has_no_data()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  if (edge->has_data())
  {
    std::cerr << "Error: A new edge has data in it" << std::endl;
  }
}

void
test_new_is_not_full()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  if (edge->full_of_data())
  {
    std::cerr << "Error: A new edge is full of data" << std::endl;
  }
}

void
test_new_has_count_zero()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  if (edge->datum_count())
  {
    std::cerr << "Error: A new edge has a count" << std::endl;
  }
}

void
test_push_datum()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  vistk::datum_t const dat = vistk::datum::complete_datum();
  vistk::stamp_t const stamp = vistk::stamp::new_stamp();

  vistk::edge_datum_t const edat = vistk::edge_datum_t(dat, stamp);

  edge->push_datum(edat);

  if (edge->datum_count() != 1)
  {
    std::cerr << "Error: An edge with a pushed datum does not have a count of one" << std::endl;
  }

  edge->push_datum(edat);

  if (edge->datum_count() != 2)
  {
    std::cerr << "Error: An edge with two pushed data does not have a count of two" << std::endl;
  }
}

void
test_peek_datum()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  vistk::datum_t const dat = vistk::datum::complete_datum();
  vistk::stamp_t const stamp = vistk::stamp::new_stamp();

  vistk::edge_datum_t const edat = vistk::edge_datum_t(dat, stamp);

  edge->push_datum(edat);

  vistk::edge_datum_t const get_edat = edge->peek_datum();

  if (edge->datum_count() != 1)
  {
    std::cerr << "Error: An edge removed a datum on an peek" << std::endl;
  }

  if (*get_edat.get<1>() != *stamp)
  {
    std::cerr << "Error: The edge modified a stamp on a peek" << std::endl;
  }
}

void
test_pop_datum()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  vistk::datum_t const dat = vistk::datum::complete_datum();
  vistk::stamp_t const stamp = vistk::stamp::new_stamp();

  vistk::edge_datum_t const edat = vistk::edge_datum_t(dat, stamp);

  edge->push_datum(edat);

  edge->pop_datum();

  if (edge->datum_count())
  {
    std::cerr << "Error: An edge did not remove a datum on a pop" << std::endl;
  }
}

void
test_get_datum()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  vistk::datum_t const dat = vistk::datum::complete_datum();
  vistk::stamp_t const stamp = vistk::stamp::new_stamp();

  vistk::edge_datum_t const edat = vistk::edge_datum_t(dat, stamp);

  edge->push_datum(edat);

  vistk::edge_datum_t const get_edat = edge->get_datum();

  if (edge->datum_count() != 0)
  {
    std::cerr << "Error: An edge did not remove a datum on a get" << std::endl;
  }

  if (*get_edat.get<1>() != *stamp)
  {
    std::cerr << "Error: The edge modified a stamp on a get" << std::endl;
  }
}

void
test_required_by_downstream()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  if (!edge->required_by_downstream())
  {
    std::cerr << "Error: A new edge is not marked as required by downstream" << std::endl;
  }

  edge->set_required_by_downstream(false);

  if (edge->required_by_downstream())
  {
    std::cerr << "Error: Setting the requirement by downstream failed" << std::endl;
  }

  edge->set_required_by_downstream(true);

  if (!edge->required_by_downstream())
  {
    std::cerr << "Error: Setting the requirement by downstream failed" << std::endl;
  }
}

void
test_null_upstream_process()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  vistk::process_t const process;

  EXPECT_EXCEPTION(vistk::null_process_connection_exception,
                   edge->set_upstream_process(process),
                   "setting a NULL process as upstream");
}

void
test_null_downstream_process()
{
  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  vistk::process_t const process;

  EXPECT_EXCEPTION(vistk::null_process_connection_exception,
                   edge->set_downstream_process(process),
                   "setting a NULL process as downstream");
}

void
test_set_upstream_process()
{
  vistk::load_known_modules();

  vistk::process_registry_t const reg = vistk::process_registry::self();
  vistk::process_registry::type_t const proc_type = vistk::process_registry::type_t("numbers");

  vistk::config_t const proc_config = vistk::config::empty_config();

  vistk::process_t const process = reg->create_process(proc_type, proc_config);

  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  edge->set_upstream_process(process);

  EXPECT_EXCEPTION(vistk::input_already_connected_exception,
                   edge->set_upstream_process(process),
                   "setting a second process as upstream");
}

void
test_set_downstream_process()
{
  vistk::load_known_modules();

  vistk::process_registry_t const reg = vistk::process_registry::self();
  vistk::process_registry::type_t const proc_type = vistk::process_registry::type_t("numbers");

  vistk::config_t const proc_config = vistk::config::empty_config();

  vistk::process_t const process = reg->create_process(proc_type, proc_config);

  vistk::config_t const config = vistk::config::empty_config();

  vistk::edge_t edge = vistk::edge_t(new vistk::edge(config));

  edge->set_downstream_process(process);

  EXPECT_EXCEPTION(vistk::output_already_connected_exception,
                   edge->set_downstream_process(process),
                   "setting a second process as downstream");
}
<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2018, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file
 *   This file implements Channel Manager.
 *
 */

#include "channel_manager.hpp"

#include "common/code_utils.hpp"
#include "common/instance.hpp"
#include "common/locator-getters.hpp"
#include "common/logging.hpp"
#include "common/random.hpp"
#include "phy/phy.hpp"

#if OPENTHREAD_ENABLE_CHANNEL_MANAGER && OPENTHREAD_FTD

namespace ot {
namespace Utils {

ChannelManager::ChannelManager(Instance &aInstance)
    : InstanceLocator(aInstance)
    , mSupportedChannelMask(0)
    , mFavoredChannelMask(0)
    , mActiveTimestamp(0)
    , mNotifierCallback(aInstance, &ChannelManager::HandleStateChanged, this)
    , mDelay(kMinimumDelay)
    , mChannel(0)
    , mState(kStateIdle)
    , mTimer(aInstance, &ChannelManager::HandleTimer, this)
    , mAutoSelectInterval(kDefaultAutoSelectInterval)
    , mAutoSelectEnabled(false)
{
}

void ChannelManager::RequestChannelChange(uint8_t aChannel)
{
    otLogInfoUtil("ChannelManager: Request to change to channel %d with delay %d sec", aChannel, mDelay);

    if (aChannel == Get<Mac::Mac>().GetPanChannel())
    {
        otLogInfoUtil("ChannelManager: Already operating on the requested channel %d", aChannel);
        ExitNow();
    }

    mState           = kStateChangeRequested;
    mChannel         = aChannel;
    mActiveTimestamp = 0;

    mTimer.Start(1 + Random::NonCrypto::GetUint32InRange(0, kRequestStartJitterInterval));

    Get<Notifier>().Signal(OT_CHANGED_CHANNEL_MANAGER_NEW_CHANNEL);

exit:
    return;
}

otError ChannelManager::SetDelay(uint16_t aDelay)
{
    otError error = OT_ERROR_NONE;

    VerifyOrExit(aDelay >= kMinimumDelay, error = OT_ERROR_INVALID_ARGS);
    mDelay = aDelay;

exit:
    return error;
}

void ChannelManager::PreparePendingDataset(void)
{
    uint64_t             pendingTimestamp       = 0;
    uint64_t             pendingActiveTimestamp = 0;
    uint32_t             delayInMs              = TimerMilli::SecToMsec(static_cast<uint32_t>(mDelay));
    otOperationalDataset dataset;
    otError              error;

    VerifyOrExit(mState == kStateChangeRequested);

    VerifyOrExit(mChannel != Get<Mac::Mac>().GetPanChannel());

    if (Get<MeshCoP::PendingDataset>().Read(dataset) == OT_ERROR_NONE)
    {
        if (dataset.mComponents.mIsPendingTimestampPresent)
        {
            pendingTimestamp = dataset.mPendingTimestamp;
        }

        // We check whether the Pending Dataset is changing the
        // channel to same one as the current request (i.e., channel
        // should match and delay should be less than the requested
        // delay).

        if (dataset.mComponents.mIsChannelPresent && (mChannel == dataset.mChannel) &&
            dataset.mComponents.mIsDelayPresent && (dataset.mDelay <= delayInMs) &&
            dataset.mComponents.mIsActiveTimestampPresent)
        {
            // We save the active timestamp to later check and ensure it
            // is ahead of current ActiveDataset timestamp.

            pendingActiveTimestamp = dataset.mActiveTimestamp;
        }
    }

    pendingTimestamp += 1 + Random::NonCrypto::GetUint32InRange(0, kMaxTimestampIncrease);

    error = Get<MeshCoP::ActiveDataset>().Read(dataset);

    if (error != OT_ERROR_NONE)
    {
        // If there is no valid Active Dataset but we are not disabled, set
        // the timer to try again after the retry interval. This handles the
        // situation where a channel change request comes right after the
        // network is formed but before the active dataset is created.

        if (Get<Mle::Mle>().GetRole() != OT_DEVICE_ROLE_DISABLED)
        {
            mTimer.Start(kPendingDatasetTxRetryInterval);
        }
        else
        {
            otLogInfoUtil("ChannelManager: Request to change to channel %d failed. Device is disabled", mChannel);

            mState = kStateIdle;
            StartAutoSelectTimer();
        }

        ExitNow();
    }

    // `pendingActiveTimestamp` will be non-zero if the Pending
    // Dataset is valid and is performing the same channel change.
    // We check to ensure its timestamp is indeed ahead of current
    // Active Dataset's timestamp, and if so, we skip updating
    // the Pending Dataset.

    if (pendingActiveTimestamp != 0)
    {
        if (dataset.mActiveTimestamp < pendingActiveTimestamp)
        {
            otLogInfoUtil("ChannelManager: Pending Dataset is valid for change channel to %d", mChannel);
            mState = kStateSentMgmtPendingDataset;
            mTimer.Start(delayInMs + kChangeCheckWaitInterval);
            ExitNow();
        }
    }

    // A non-zero `mActiveTimestamp` indicates that this is not the first
    // attempt to update the Dataset for the ongoing requested channel
    // change. In that case, if the Timestamp in current Active Dataset is
    // more recent compared to `mActiveTimestamp`, the channel change
    // process is canceled. This helps address situations where two
    // different devices may be performing channel change around the same
    // time.

    if (mActiveTimestamp != 0)
    {
        if (dataset.mActiveTimestamp >= mActiveTimestamp)
        {
            otLogInfoUtil("ChannelManager: Canceling channel change to %d since current ActiveDataset is more recent",
                          mChannel);

            ExitNow();
        }
    }
    else
    {
        mActiveTimestamp = dataset.mActiveTimestamp + 1 + Random::NonCrypto::GetUint32InRange(0, kMaxTimestampIncrease);
    }

    dataset.mActiveTimestamp                       = mActiveTimestamp;
    dataset.mComponents.mIsActiveTimestampPresent  = true;
    dataset.mChannel                               = mChannel;
    dataset.mComponents.mIsChannelPresent          = true;
    dataset.mPendingTimestamp                      = pendingTimestamp;
    dataset.mComponents.mIsPendingTimestampPresent = true;
    dataset.mDelay                                 = delayInMs;
    dataset.mComponents.mIsDelayPresent            = true;

    error = Get<MeshCoP::PendingDataset>().SendSetRequest(dataset, NULL, 0);

    if (error == OT_ERROR_NONE)
    {
        otLogInfoUtil("ChannelManager: Sent PendingDatasetSet to change channel to %d", mChannel);

        mState = kStateSentMgmtPendingDataset;
        mTimer.Start(delayInMs + kChangeCheckWaitInterval);
    }
    else
    {
        otLogInfoUtil("ChannelManager: %s error in dataset update (channel change %d), retry in %d sec",
                      otThreadErrorToString(error), mChannel, TimerMilli::MsecToSec(kPendingDatasetTxRetryInterval));

        mTimer.Start(kPendingDatasetTxRetryInterval);
    }

exit:
    return;
}

void ChannelManager::HandleTimer(Timer &aTimer)
{
    aTimer.GetOwner<ChannelManager>().HandleTimer();
}

void ChannelManager::HandleTimer(void)
{
    switch (mState)
    {
    case kStateIdle:
        otLogInfoUtil("ChannelManager: Auto-triggered channel select");
        IgnoreReturnValue(RequestChannelSelect(false));
        StartAutoSelectTimer();
        break;

    case kStateSentMgmtPendingDataset:
        otLogInfoUtil("ChannelManager: Timed out waiting for change to %d, trying again.", mChannel);
        mState = kStateChangeRequested;

        // fall through

    case kStateChangeRequested:
        PreparePendingDataset();
        break;
    }
}

void ChannelManager::HandleStateChanged(Notifier::Callback &aCallback, otChangedFlags aChangedFlags)
{
    aCallback.GetOwner<ChannelManager>().HandleStateChanged(aChangedFlags);
}

void ChannelManager::HandleStateChanged(otChangedFlags aChangedFlags)
{
    VerifyOrExit((aChangedFlags & OT_CHANGED_THREAD_CHANNEL) != 0);
    VerifyOrExit(mChannel == Get<Mac::Mac>().GetPanChannel());

    mState = kStateIdle;
    StartAutoSelectTimer();

    otLogInfoUtil("ChannelManager: Channel successfully changed to %d", mChannel);

exit:
    return;
}

#if OPENTHREAD_ENABLE_CHANNEL_MONITOR

otError ChannelManager::FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy)
{
    otError          error = OT_ERROR_NONE;
    Mac::ChannelMask favoredAndSupported;
    Mac::ChannelMask favoredBest;
    Mac::ChannelMask supportedBest;
    uint16_t         favoredOccupancy;
    uint16_t         supportedOccupancy;

    if (Get<ChannelMonitor>().GetSampleCount() <= kMinChannelMonitorSampleCount)
    {
        otLogInfoUtil("ChannelManager: Too few samples (%d <= %d) to select channel",
                      Get<ChannelMonitor>().GetSampleCount(), kMinChannelMonitorSampleCount);
        ExitNow(error = OT_ERROR_INVALID_STATE);
    }

    favoredAndSupported = mFavoredChannelMask;
    favoredAndSupported.Intersect(mSupportedChannelMask);

    favoredBest   = Get<ChannelMonitor>().FindBestChannels(favoredAndSupported, favoredOccupancy);
    supportedBest = Get<ChannelMonitor>().FindBestChannels(mSupportedChannelMask, supportedOccupancy);

    otLogInfoUtil("ChannelManager: Best favored %s, occupancy 0x%04x", favoredBest.ToString().AsCString(),
                  favoredOccupancy);
    otLogInfoUtil("ChannelManager: Best overall %s, occupancy 0x%04x", supportedBest.ToString().AsCString(),
                  supportedOccupancy);

    // Prefer favored channels unless there is no favored channel,
    // or the occupancy rate of the best favored channel is worse
    // than the best overall by at least `kThresholdToSkipFavored`.

    if (favoredBest.IsEmpty() || ((favoredOccupancy >= kThresholdToSkipFavored) &&
                                  (supportedOccupancy < favoredOccupancy - kThresholdToSkipFavored)))
    {
        if (!favoredBest.IsEmpty())
        {
            otLogInfoUtil("ChannelManager: Preferring an unfavored channel due to high occupancy rate diff");
        }

        favoredBest      = supportedBest;
        favoredOccupancy = supportedOccupancy;
    }

    VerifyOrExit(!favoredBest.IsEmpty(), error = OT_ERROR_NOT_FOUND);

    aNewChannel = favoredBest.ChooseRandomChannel();
    aOccupancy  = favoredOccupancy;

exit:
    return error;
}

bool ChannelManager::ShouldAttemptChannelChange(void)
{
    uint16_t ccaFailureRate = Get<Mac::Mac>().GetCcaFailureRate();
    bool     shouldAttempt  = (ccaFailureRate >= kCcaFailureRateThreshold);

    otLogInfoUtil("ChannelManager: CCA-err-rate: 0x%04x %s 0x%04x, selecting channel: %s", ccaFailureRate,
                  shouldAttempt ? ">=" : "<", kCcaFailureRateThreshold, shouldAttempt ? "yes" : "no");

    return shouldAttempt;
}

otError ChannelManager::RequestChannelSelect(bool aSkipQualityCheck)
{
    otError  error = OT_ERROR_NONE;
    uint8_t  curChannel, newChannel;
    uint16_t curOccupancy, newOccupancy;

    otLogInfoUtil("ChannelManager: Request to select channel (skip quality check: %s)",
                  aSkipQualityCheck ? "yes" : "no");

    VerifyOrExit(Get<Mle::Mle>().GetRole() != OT_DEVICE_ROLE_DISABLED, error = OT_ERROR_INVALID_STATE);

    VerifyOrExit(aSkipQualityCheck || ShouldAttemptChannelChange());

    SuccessOrExit(error = FindBetterChannel(newChannel, newOccupancy));

    curChannel   = Get<Mac::Mac>().GetPanChannel();
    curOccupancy = Get<ChannelMonitor>().GetChannelOccupancy(curChannel);

    if (newChannel == curChannel)
    {
        otLogInfoUtil("ChannelManager: Already on best possible channel %d", curChannel);
        ExitNow();
    }

    otLogInfoUtil("ChannelManager: Cur channel %d, occupancy 0x%04x - Best channel %d, occupancy 0x%04x", curChannel,
                  curOccupancy, newChannel, newOccupancy);

    // Switch only if new channel's occupancy rate is better than current
    // channel's occupancy rate by threshold `kThresholdToChangeChannel`.

    if ((newOccupancy >= curOccupancy) ||
        (static_cast<uint16_t>(curOccupancy - newOccupancy) < kThresholdToChangeChannel))
    {
        otLogInfoUtil("ChannelManager: Occupancy rate diff too small to change channel");
        ExitNow();
    }

    RequestChannelChange(newChannel);

exit:

    if (error != OT_ERROR_NONE)
    {
        otLogInfoUtil("ChannelManager: Request to select better channel failed, error: %s",
                      otThreadErrorToString(error));
    }

    return error;
}

#else // OPENTHREAD_ENABLE_CHANNEL_MONITOR

otError ChannelManager::RequestChannelSelect(bool)
{
    otLogInfoUtil("ChannelManager: ChannelMonitor feature is disabled - cannot select channel");
    return OT_ERROR_DISABLED_FEATURE;
}

#endif // OPENTHREAD_ENABLE_CHANNEL_MONITOR

void ChannelManager::StartAutoSelectTimer(void)
{
    VerifyOrExit(mState == kStateIdle);

    if (mAutoSelectEnabled)
    {
        mTimer.Start(TimerMilli::SecToMsec(mAutoSelectInterval));
    }
    else
    {
        mTimer.Stop();
    }

exit:
    return;
}

void ChannelManager::SetAutoChannelSelectionEnabled(bool aEnabled)
{
    if (aEnabled != mAutoSelectEnabled)
    {
        mAutoSelectEnabled = aEnabled;
        IgnoreReturnValue(RequestChannelSelect(false));
        StartAutoSelectTimer();
    }
}

otError ChannelManager::SetAutoChannelSelectionInterval(uint32_t aInterval)
{
    otError  error        = OT_ERROR_NONE;
    uint32_t prevInterval = mAutoSelectInterval;

    VerifyOrExit((aInterval != 0) && (aInterval < TimerMilli::MsecToSec(Timer::kMaxDt)), error = OT_ERROR_INVALID_ARGS);

    mAutoSelectInterval = aInterval;

    if (mAutoSelectEnabled && (mState == kStateIdle) && mTimer.IsRunning() && (prevInterval != aInterval))
    {
        mTimer.StartAt(mTimer.GetFireTime() - prevInterval, aInterval);
    }

exit:
    return error;
}

void ChannelManager::SetSupportedChannels(uint32_t aChannelMask)
{
    mSupportedChannelMask.SetMask(aChannelMask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());

    otLogInfoUtil("ChannelManager: Supported channels: %s", mSupportedChannelMask.ToString().AsCString());
}

void ChannelManager::SetFavoredChannels(uint32_t aChannelMask)
{
    mFavoredChannelMask.SetMask(aChannelMask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());

    otLogInfoUtil("ChannelManager: Favored channels: %s", mFavoredChannelMask.ToString().AsCString());
}

} // namespace Utils
} // namespace ot

#endif // #if OPENTHREAD_ENABLE_CHANNEL_MANAGER
<commit_msg>[channel-manager] fix interval units (#3857)<commit_after>/*
 *  Copyright (c) 2018, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file
 *   This file implements Channel Manager.
 *
 */

#include "channel_manager.hpp"

#include "common/code_utils.hpp"
#include "common/instance.hpp"
#include "common/locator-getters.hpp"
#include "common/logging.hpp"
#include "common/random.hpp"
#include "phy/phy.hpp"

#if OPENTHREAD_ENABLE_CHANNEL_MANAGER && OPENTHREAD_FTD

namespace ot {
namespace Utils {

ChannelManager::ChannelManager(Instance &aInstance)
    : InstanceLocator(aInstance)
    , mSupportedChannelMask(0)
    , mFavoredChannelMask(0)
    , mActiveTimestamp(0)
    , mNotifierCallback(aInstance, &ChannelManager::HandleStateChanged, this)
    , mDelay(kMinimumDelay)
    , mChannel(0)
    , mState(kStateIdle)
    , mTimer(aInstance, &ChannelManager::HandleTimer, this)
    , mAutoSelectInterval(kDefaultAutoSelectInterval)
    , mAutoSelectEnabled(false)
{
}

void ChannelManager::RequestChannelChange(uint8_t aChannel)
{
    otLogInfoUtil("ChannelManager: Request to change to channel %d with delay %d sec", aChannel, mDelay);

    if (aChannel == Get<Mac::Mac>().GetPanChannel())
    {
        otLogInfoUtil("ChannelManager: Already operating on the requested channel %d", aChannel);
        ExitNow();
    }

    mState           = kStateChangeRequested;
    mChannel         = aChannel;
    mActiveTimestamp = 0;

    mTimer.Start(1 + Random::NonCrypto::GetUint32InRange(0, kRequestStartJitterInterval));

    Get<Notifier>().Signal(OT_CHANGED_CHANNEL_MANAGER_NEW_CHANNEL);

exit:
    return;
}

otError ChannelManager::SetDelay(uint16_t aDelay)
{
    otError error = OT_ERROR_NONE;

    VerifyOrExit(aDelay >= kMinimumDelay, error = OT_ERROR_INVALID_ARGS);
    mDelay = aDelay;

exit:
    return error;
}

void ChannelManager::PreparePendingDataset(void)
{
    uint64_t             pendingTimestamp       = 0;
    uint64_t             pendingActiveTimestamp = 0;
    uint32_t             delayInMs              = TimerMilli::SecToMsec(static_cast<uint32_t>(mDelay));
    otOperationalDataset dataset;
    otError              error;

    VerifyOrExit(mState == kStateChangeRequested);

    VerifyOrExit(mChannel != Get<Mac::Mac>().GetPanChannel());

    if (Get<MeshCoP::PendingDataset>().Read(dataset) == OT_ERROR_NONE)
    {
        if (dataset.mComponents.mIsPendingTimestampPresent)
        {
            pendingTimestamp = dataset.mPendingTimestamp;
        }

        // We check whether the Pending Dataset is changing the
        // channel to same one as the current request (i.e., channel
        // should match and delay should be less than the requested
        // delay).

        if (dataset.mComponents.mIsChannelPresent && (mChannel == dataset.mChannel) &&
            dataset.mComponents.mIsDelayPresent && (dataset.mDelay <= delayInMs) &&
            dataset.mComponents.mIsActiveTimestampPresent)
        {
            // We save the active timestamp to later check and ensure it
            // is ahead of current ActiveDataset timestamp.

            pendingActiveTimestamp = dataset.mActiveTimestamp;
        }
    }

    pendingTimestamp += 1 + Random::NonCrypto::GetUint32InRange(0, kMaxTimestampIncrease);

    error = Get<MeshCoP::ActiveDataset>().Read(dataset);

    if (error != OT_ERROR_NONE)
    {
        // If there is no valid Active Dataset but we are not disabled, set
        // the timer to try again after the retry interval. This handles the
        // situation where a channel change request comes right after the
        // network is formed but before the active dataset is created.

        if (Get<Mle::Mle>().GetRole() != OT_DEVICE_ROLE_DISABLED)
        {
            mTimer.Start(kPendingDatasetTxRetryInterval);
        }
        else
        {
            otLogInfoUtil("ChannelManager: Request to change to channel %d failed. Device is disabled", mChannel);

            mState = kStateIdle;
            StartAutoSelectTimer();
        }

        ExitNow();
    }

    // `pendingActiveTimestamp` will be non-zero if the Pending
    // Dataset is valid and is performing the same channel change.
    // We check to ensure its timestamp is indeed ahead of current
    // Active Dataset's timestamp, and if so, we skip updating
    // the Pending Dataset.

    if (pendingActiveTimestamp != 0)
    {
        if (dataset.mActiveTimestamp < pendingActiveTimestamp)
        {
            otLogInfoUtil("ChannelManager: Pending Dataset is valid for change channel to %d", mChannel);
            mState = kStateSentMgmtPendingDataset;
            mTimer.Start(delayInMs + kChangeCheckWaitInterval);
            ExitNow();
        }
    }

    // A non-zero `mActiveTimestamp` indicates that this is not the first
    // attempt to update the Dataset for the ongoing requested channel
    // change. In that case, if the Timestamp in current Active Dataset is
    // more recent compared to `mActiveTimestamp`, the channel change
    // process is canceled. This helps address situations where two
    // different devices may be performing channel change around the same
    // time.

    if (mActiveTimestamp != 0)
    {
        if (dataset.mActiveTimestamp >= mActiveTimestamp)
        {
            otLogInfoUtil("ChannelManager: Canceling channel change to %d since current ActiveDataset is more recent",
                          mChannel);

            ExitNow();
        }
    }
    else
    {
        mActiveTimestamp = dataset.mActiveTimestamp + 1 + Random::NonCrypto::GetUint32InRange(0, kMaxTimestampIncrease);
    }

    dataset.mActiveTimestamp                       = mActiveTimestamp;
    dataset.mComponents.mIsActiveTimestampPresent  = true;
    dataset.mChannel                               = mChannel;
    dataset.mComponents.mIsChannelPresent          = true;
    dataset.mPendingTimestamp                      = pendingTimestamp;
    dataset.mComponents.mIsPendingTimestampPresent = true;
    dataset.mDelay                                 = delayInMs;
    dataset.mComponents.mIsDelayPresent            = true;

    error = Get<MeshCoP::PendingDataset>().SendSetRequest(dataset, NULL, 0);

    if (error == OT_ERROR_NONE)
    {
        otLogInfoUtil("ChannelManager: Sent PendingDatasetSet to change channel to %d", mChannel);

        mState = kStateSentMgmtPendingDataset;
        mTimer.Start(delayInMs + kChangeCheckWaitInterval);
    }
    else
    {
        otLogInfoUtil("ChannelManager: %s error in dataset update (channel change %d), retry in %d sec",
                      otThreadErrorToString(error), mChannel, TimerMilli::MsecToSec(kPendingDatasetTxRetryInterval));

        mTimer.Start(kPendingDatasetTxRetryInterval);
    }

exit:
    return;
}

void ChannelManager::HandleTimer(Timer &aTimer)
{
    aTimer.GetOwner<ChannelManager>().HandleTimer();
}

void ChannelManager::HandleTimer(void)
{
    switch (mState)
    {
    case kStateIdle:
        otLogInfoUtil("ChannelManager: Auto-triggered channel select");
        IgnoreReturnValue(RequestChannelSelect(false));
        StartAutoSelectTimer();
        break;

    case kStateSentMgmtPendingDataset:
        otLogInfoUtil("ChannelManager: Timed out waiting for change to %d, trying again.", mChannel);
        mState = kStateChangeRequested;

        // fall through

    case kStateChangeRequested:
        PreparePendingDataset();
        break;
    }
}

void ChannelManager::HandleStateChanged(Notifier::Callback &aCallback, otChangedFlags aChangedFlags)
{
    aCallback.GetOwner<ChannelManager>().HandleStateChanged(aChangedFlags);
}

void ChannelManager::HandleStateChanged(otChangedFlags aChangedFlags)
{
    VerifyOrExit((aChangedFlags & OT_CHANGED_THREAD_CHANNEL) != 0);
    VerifyOrExit(mChannel == Get<Mac::Mac>().GetPanChannel());

    mState = kStateIdle;
    StartAutoSelectTimer();

    otLogInfoUtil("ChannelManager: Channel successfully changed to %d", mChannel);

exit:
    return;
}

#if OPENTHREAD_ENABLE_CHANNEL_MONITOR

otError ChannelManager::FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy)
{
    otError          error = OT_ERROR_NONE;
    Mac::ChannelMask favoredAndSupported;
    Mac::ChannelMask favoredBest;
    Mac::ChannelMask supportedBest;
    uint16_t         favoredOccupancy;
    uint16_t         supportedOccupancy;

    if (Get<ChannelMonitor>().GetSampleCount() <= kMinChannelMonitorSampleCount)
    {
        otLogInfoUtil("ChannelManager: Too few samples (%d <= %d) to select channel",
                      Get<ChannelMonitor>().GetSampleCount(), kMinChannelMonitorSampleCount);
        ExitNow(error = OT_ERROR_INVALID_STATE);
    }

    favoredAndSupported = mFavoredChannelMask;
    favoredAndSupported.Intersect(mSupportedChannelMask);

    favoredBest   = Get<ChannelMonitor>().FindBestChannels(favoredAndSupported, favoredOccupancy);
    supportedBest = Get<ChannelMonitor>().FindBestChannels(mSupportedChannelMask, supportedOccupancy);

    otLogInfoUtil("ChannelManager: Best favored %s, occupancy 0x%04x", favoredBest.ToString().AsCString(),
                  favoredOccupancy);
    otLogInfoUtil("ChannelManager: Best overall %s, occupancy 0x%04x", supportedBest.ToString().AsCString(),
                  supportedOccupancy);

    // Prefer favored channels unless there is no favored channel,
    // or the occupancy rate of the best favored channel is worse
    // than the best overall by at least `kThresholdToSkipFavored`.

    if (favoredBest.IsEmpty() || ((favoredOccupancy >= kThresholdToSkipFavored) &&
                                  (supportedOccupancy < favoredOccupancy - kThresholdToSkipFavored)))
    {
        if (!favoredBest.IsEmpty())
        {
            otLogInfoUtil("ChannelManager: Preferring an unfavored channel due to high occupancy rate diff");
        }

        favoredBest      = supportedBest;
        favoredOccupancy = supportedOccupancy;
    }

    VerifyOrExit(!favoredBest.IsEmpty(), error = OT_ERROR_NOT_FOUND);

    aNewChannel = favoredBest.ChooseRandomChannel();
    aOccupancy  = favoredOccupancy;

exit:
    return error;
}

bool ChannelManager::ShouldAttemptChannelChange(void)
{
    uint16_t ccaFailureRate = Get<Mac::Mac>().GetCcaFailureRate();
    bool     shouldAttempt  = (ccaFailureRate >= kCcaFailureRateThreshold);

    otLogInfoUtil("ChannelManager: CCA-err-rate: 0x%04x %s 0x%04x, selecting channel: %s", ccaFailureRate,
                  shouldAttempt ? ">=" : "<", kCcaFailureRateThreshold, shouldAttempt ? "yes" : "no");

    return shouldAttempt;
}

otError ChannelManager::RequestChannelSelect(bool aSkipQualityCheck)
{
    otError  error = OT_ERROR_NONE;
    uint8_t  curChannel, newChannel;
    uint16_t curOccupancy, newOccupancy;

    otLogInfoUtil("ChannelManager: Request to select channel (skip quality check: %s)",
                  aSkipQualityCheck ? "yes" : "no");

    VerifyOrExit(Get<Mle::Mle>().GetRole() != OT_DEVICE_ROLE_DISABLED, error = OT_ERROR_INVALID_STATE);

    VerifyOrExit(aSkipQualityCheck || ShouldAttemptChannelChange());

    SuccessOrExit(error = FindBetterChannel(newChannel, newOccupancy));

    curChannel   = Get<Mac::Mac>().GetPanChannel();
    curOccupancy = Get<ChannelMonitor>().GetChannelOccupancy(curChannel);

    if (newChannel == curChannel)
    {
        otLogInfoUtil("ChannelManager: Already on best possible channel %d", curChannel);
        ExitNow();
    }

    otLogInfoUtil("ChannelManager: Cur channel %d, occupancy 0x%04x - Best channel %d, occupancy 0x%04x", curChannel,
                  curOccupancy, newChannel, newOccupancy);

    // Switch only if new channel's occupancy rate is better than current
    // channel's occupancy rate by threshold `kThresholdToChangeChannel`.

    if ((newOccupancy >= curOccupancy) ||
        (static_cast<uint16_t>(curOccupancy - newOccupancy) < kThresholdToChangeChannel))
    {
        otLogInfoUtil("ChannelManager: Occupancy rate diff too small to change channel");
        ExitNow();
    }

    RequestChannelChange(newChannel);

exit:

    if (error != OT_ERROR_NONE)
    {
        otLogInfoUtil("ChannelManager: Request to select better channel failed, error: %s",
                      otThreadErrorToString(error));
    }

    return error;
}

#else // OPENTHREAD_ENABLE_CHANNEL_MONITOR

otError ChannelManager::RequestChannelSelect(bool)
{
    otLogInfoUtil("ChannelManager: ChannelMonitor feature is disabled - cannot select channel");
    return OT_ERROR_DISABLED_FEATURE;
}

#endif // OPENTHREAD_ENABLE_CHANNEL_MONITOR

void ChannelManager::StartAutoSelectTimer(void)
{
    VerifyOrExit(mState == kStateIdle);

    if (mAutoSelectEnabled)
    {
        mTimer.Start(TimerMilli::SecToMsec(mAutoSelectInterval));
    }
    else
    {
        mTimer.Stop();
    }

exit:
    return;
}

void ChannelManager::SetAutoChannelSelectionEnabled(bool aEnabled)
{
    if (aEnabled != mAutoSelectEnabled)
    {
        mAutoSelectEnabled = aEnabled;
        IgnoreReturnValue(RequestChannelSelect(false));
        StartAutoSelectTimer();
    }
}

otError ChannelManager::SetAutoChannelSelectionInterval(uint32_t aInterval)
{
    otError  error        = OT_ERROR_NONE;
    uint32_t prevInterval = mAutoSelectInterval;

    VerifyOrExit((aInterval != 0) && (aInterval <= TimerMilli::MsecToSec(Timer::kMaxDt)),
                 error = OT_ERROR_INVALID_ARGS);

    mAutoSelectInterval = aInterval;

    if (mAutoSelectEnabled && (mState == kStateIdle) && mTimer.IsRunning() && (prevInterval != aInterval))
    {
        mTimer.StartAt(mTimer.GetFireTime() - TimerMilli::SecToMsec(prevInterval), TimerMilli::SecToMsec(aInterval));
    }

exit:
    return error;
}

void ChannelManager::SetSupportedChannels(uint32_t aChannelMask)
{
    mSupportedChannelMask.SetMask(aChannelMask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());

    otLogInfoUtil("ChannelManager: Supported channels: %s", mSupportedChannelMask.ToString().AsCString());
}

void ChannelManager::SetFavoredChannels(uint32_t aChannelMask)
{
    mFavoredChannelMask.SetMask(aChannelMask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());

    otLogInfoUtil("ChannelManager: Favored channels: %s", mFavoredChannelMask.ToString().AsCString());
}

} // namespace Utils
} // namespace ot

#endif // #if OPENTHREAD_ENABLE_CHANNEL_MANAGER
<|endoftext|>