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

#include "src/trace_processor/importers/common/clock_tracker.h"

#include <inttypes.h>

#include <algorithm>
#include <queue>

#include "perfetto/base/logging.h"
#include "perfetto/ext/base/hash.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"

#include "protos/perfetto/common/builtin_clock.pbzero.h"
#include "protos/perfetto/trace/clock_snapshot.pbzero.h"

namespace perfetto {
namespace trace_processor {

using Clock = protos::pbzero::ClockSnapshot::Clock;

ClockTracker::ClockTracker(TraceProcessorContext* ctx)
    : context_(ctx),
      trace_time_clock_id_(protos::pbzero::BUILTIN_CLOCK_BOOTTIME) {}

ClockTracker::~ClockTracker() = default;

void ClockTracker::AddSnapshot(const std::vector<ClockValue>& clocks) {
  const auto snapshot_id = cur_snapshot_id_++;

  // Clear the cache
  cache_.fill({});

  // Compute the fingerprint of the snapshot by hashing all clock ids. This is
  // used by the clock pathfinding logic.
  base::Hash hasher;
  for (const auto& clock : clocks)
    hasher.Update(clock.clock_id);
  const auto snapshot_hash = static_cast<SnapshotHash>(hasher.digest());

  // Add a new entry in each clock's snapshot vector.
  for (const auto& clock : clocks) {
    ClockId clock_id = clock.clock_id;
    ClockDomain& domain = clocks_[clock_id];
    if (domain.snapshots.empty()) {
      if (clock.is_incremental && !ClockIsSeqScoped(clock_id)) {
        PERFETTO_ELOG("Clock sync error: the global clock with id=%" PRIu64
                      " cannot use incremental encoding; this is only "
                      "supported for sequence-scoped clocks.",
                      clock_id);
        context_->storage->IncrementStats(stats::invalid_clock_snapshots);
        return;
      }
      domain.unit_multiplier_ns = clock.unit_multiplier_ns;
      domain.is_incremental = clock.is_incremental;
    } else if (PERFETTO_UNLIKELY(
                   domain.unit_multiplier_ns != clock.unit_multiplier_ns ||
                   domain.is_incremental != clock.is_incremental)) {
      PERFETTO_ELOG("Clock sync error: the clock domain with id=%" PRIu64
                    " (unit=%" PRIu64
                    ", incremental=%d), was previously registered with "
                    "different properties (unit=%" PRIu64 ", incremental=%d).",
                    clock_id, clock.unit_multiplier_ns, clock.is_incremental,
                    domain.unit_multiplier_ns, domain.is_incremental);
      context_->storage->IncrementStats(stats::invalid_clock_snapshots);
      return;
    }
    const int64_t timestamp_ns =
        clock.absolute_timestamp * domain.unit_multiplier_ns;
    domain.last_timestamp_ns = timestamp_ns;

    ClockSnapshots& vect = domain.snapshots[snapshot_hash];
    if (!vect.snapshot_ids.empty() &&
        PERFETTO_UNLIKELY(vect.snapshot_ids.back() == snapshot_id)) {
      PERFETTO_ELOG("Clock sync error: duplicate clock domain with id=%" PRIu64
                    " at snapshot %" PRIu32 ".",
                    clock_id, snapshot_id);
      context_->storage->IncrementStats(stats::invalid_clock_snapshots);
      return;
    }

    // Clock ids in the range [64, 128) are sequence-scoped and must be
    // translated to global ids via SeqScopedClockIdToGlobal() before calling
    // this function.
    PERFETTO_DCHECK(!IsReservedSeqScopedClockId(clock_id));

    // Snapshot IDs must be always monotonic.
    PERFETTO_DCHECK(vect.snapshot_ids.empty() ||
                    vect.snapshot_ids.back() < snapshot_id);

    if (!vect.timestamps_ns.empty() &&
        timestamp_ns < vect.timestamps_ns.back()) {
      // Clock is not monotonic.

      if (clock_id == trace_time_clock_id_) {
        // The trace clock cannot be non-monotonic.
        PERFETTO_ELOG("Clock sync error: the trace clock (id=%" PRIu64
                      ") is not monotonic at snapshot %" PRIu32 ". %" PRId64
                      " not >= %" PRId64 ".",
                      clock_id, snapshot_id, timestamp_ns,
                      vect.timestamps_ns.back());
        context_->storage->IncrementStats(stats::invalid_clock_snapshots);
        return;
      }

      PERFETTO_DLOG("Detected non-monotonic clock with ID %" PRIu64, clock_id);

      // For the other clocks the best thing we can do is mark it as
      // non-monotonic and refuse to use it as a source clock in the resolution
      // graph. We can still use it as a target clock, but not viceversa.
      // The concrete example is the CLOCK_REALTIME going 1h backwards during
      // daylight saving. We can still answer the question "what was the
      // REALTIME timestamp when BOOTTIME was X?" but we can't answer the
      // opposite question because there can be two valid BOOTTIME(s) for the
      // same REALTIME instant because of the 1:many relationship.
      non_monotonic_clocks_.insert(clock_id);

      // Erase all edges from the graph that start from this clock (but keep the
      // ones that end on this clock).
      auto begin = graph_.lower_bound(ClockGraphEdge{clock_id, 0, 0});
      auto end = graph_.lower_bound(ClockGraphEdge{clock_id + 1, 0, 0});
      graph_.erase(begin, end);
    }
    vect.snapshot_ids.emplace_back(snapshot_id);
    vect.timestamps_ns.emplace_back(timestamp_ns);
  }

  // Create graph edges for all the possible tuples of clocks in this snapshot.
  // If the snapshot contains clock a, b, c, d create edges [ab, ac, ad, bc, bd,
  // cd] and the symmetrical ones [ba, ca, da, bc, db, dc].
  // This is to store the information: Clock A is syncable to Clock B via the
  // snapshots of type (hash).
  // Clocks that were previously marked as non-monotonic won't be added as
  // valid sources.
  for (auto it1 = clocks.begin(); it1 != clocks.end(); ++it1) {
    auto it2 = it1;
    ++it2;
    for (; it2 != clocks.end(); ++it2) {
      if (!non_monotonic_clocks_.count(it1->clock_id))
        graph_.emplace(it1->clock_id, it2->clock_id, snapshot_hash);

      if (!non_monotonic_clocks_.count(it2->clock_id))
        graph_.emplace(it2->clock_id, it1->clock_id, snapshot_hash);
    }
  }
}

// Finds the shortest clock resolution path in the graph that allows to
// translate a timestamp from |src| to |target| clocks.
// The return value looks like the following: "If you want to convert a
// timestamp from clock C1 to C2 you need to first convert C1 -> C3 using the
// snapshot hash A, then convert C3 -> C2 via snapshot hash B".
ClockTracker::ClockPath ClockTracker::FindPath(ClockId src, ClockId target) {
  // This is a classic breadth-first search. Each node in the queue holds also
  // the full path to reach that node.
  // We assume the graph is acyclic, if it isn't the ClockPath::kMaxLen will
  // stop the search anyways.
  PERFETTO_CHECK(src != target);
  std::queue<ClockPath> queue;
  queue.emplace(src);

  while (!queue.empty()) {
    ClockPath cur_path = queue.front();
    queue.pop();

    const ClockId cur_clock_id = cur_path.last;
    if (cur_clock_id == target)
      return cur_path;

    if (cur_path.len >= ClockPath::kMaxLen)
      continue;

    // Expore all the adjacent clocks.
    // The lower_bound() below returns an iterator to the first edge that starts
    // on |cur_clock_id|. The edges are sorted by (src, target, hash).
    for (auto it = std::lower_bound(graph_.begin(), graph_.end(),
                                    ClockGraphEdge(cur_clock_id, 0, 0));
         it != graph_.end() && std::get<0>(*it) == cur_clock_id; ++it) {
      ClockId next_clock_id = std::get<1>(*it);
      SnapshotHash hash = std::get<2>(*it);
      queue.push(ClockPath(cur_path, next_clock_id, hash));
    }
  }
  return ClockPath();  // invalid path.
}

base::Optional<int64_t> ClockTracker::ConvertSlowpath(ClockId src_clock_id,
                                                      int64_t src_timestamp,
                                                      ClockId target_clock_id) {
  PERFETTO_DCHECK(!IsReservedSeqScopedClockId(src_clock_id));
  PERFETTO_DCHECK(!IsReservedSeqScopedClockId(target_clock_id));

  context_->storage->IncrementStats(stats::clock_sync_cache_miss);

  ClockPath path = FindPath(src_clock_id, target_clock_id);
  if (!path.valid()) {
    PERFETTO_DLOG("No path from clock %" PRIu64 " to %" PRIu64
                  " at timestamp %" PRId64,
                  src_clock_id, target_clock_id, src_timestamp);
    context_->storage->IncrementStats(stats::clock_sync_failure);
    return base::nullopt;
  }

  // We can cache only single-path resolutions between two clocks.
  // Caching multi-path resolutions is harder because the (src,target) tuple
  // is not enough as a cache key: at any step the |ns| value can yield to a
  // different choice of the next snapshot. Multi-path resolutions don't seem
  // too frequent these days, so we focus only on caching the more frequent
  // one-step resolutions (typically from any clock to the trace clock).
  const bool cacheable = path.len == 1;
  CachedClockPath cache_entry{};

  // Iterate trough the path found and translate timestamps onto the new clock
  // domain on each step, until the target domain is reached.
  ClockDomain* src_domain = GetClock(src_clock_id);
  int64_t ns = src_domain->ToNs(src_timestamp);
  for (uint32_t i = 0; i < path.len; ++i) {
    const ClockGraphEdge edge = path.at(i);
    ClockDomain* cur_clock = GetClock(std::get<0>(edge));
    ClockDomain* next_clock = GetClock(std::get<1>(edge));
    const SnapshotHash hash = std::get<2>(edge);

    // Find the closest timestamp within the snapshots of the source clock.
    const ClockSnapshots& cur_snap = cur_clock->GetSnapshot(hash);
    const auto& ts_vec = cur_snap.timestamps_ns;
    auto it = std::upper_bound(ts_vec.begin(), ts_vec.end(), ns);
    if (it != ts_vec.begin())
      --it;

    // Now lookup the snapshot id that matches the closest timestamp.
    size_t index = static_cast<size_t>(std::distance(ts_vec.begin(), it));
    PERFETTO_DCHECK(index < ts_vec.size());
    PERFETTO_DCHECK(cur_snap.snapshot_ids.size() == ts_vec.size());
    uint32_t snapshot_id = cur_snap.snapshot_ids[index];

    // And use that to retrieve the corresponding time in the next clock domain.
    // The snapshot id must exist in the target clock domain. If it doesn't
    // either the hash logic or the pathfinding logic are bugged.
    // This can also happen if the checks in AddSnapshot fail and we skip part
    // of the snapshot.
    const ClockSnapshots& next_snap = next_clock->GetSnapshot(hash);

    // Using std::lower_bound because snapshot_ids is sorted, so we can do
    // a binary search. std::find would do a linear scan.
    auto next_it = std::lower_bound(next_snap.snapshot_ids.begin(),
                                    next_snap.snapshot_ids.end(), snapshot_id);
    if (next_it == next_snap.snapshot_ids.end() || *next_it != snapshot_id) {
      PERFETTO_DFATAL("Snapshot does not exist in clock domain.");
      continue;
    }
    size_t next_index = static_cast<size_t>(
        std::distance(next_snap.snapshot_ids.begin(), next_it));
    PERFETTO_DCHECK(next_index < next_snap.snapshot_ids.size());
    int64_t next_timestamp_ns = next_snap.timestamps_ns[next_index];

    // The translated timestamp is the relative delta of the source timestamp
    // from the closest snapshot found (ns - *it), plus the timestamp in
    // the new clock domain for the same snapshot id.
    const int64_t adj = next_timestamp_ns - *it;
    ns += adj;

    // On the first iteration, keep track of the bounds for the cache entry.
    // This will allow future Convert() calls to skip the pathfinder logic
    // as long as the query stays within the bound.
    if (cacheable) {
      PERFETTO_DCHECK(i == 0);
      const int64_t kInt64Min = std::numeric_limits<int64_t>::min();
      const int64_t kInt64Max = std::numeric_limits<int64_t>::max();
      cache_entry.min_ts_ns = it == ts_vec.begin() ? kInt64Min : *it;
      auto ubound = it + 1;
      cache_entry.max_ts_ns = ubound == ts_vec.end() ? kInt64Max : *ubound;
      cache_entry.translation_ns = adj;
    }

    // The last clock in the path must be the target clock.
    PERFETTO_DCHECK(i < path.len - 1 || std::get<1>(edge) == target_clock_id);
  }

  if (cacheable) {
    cache_entry.src = src_clock_id;
    cache_entry.src_domain = src_domain;
    cache_entry.target = target_clock_id;
    cache_[rnd_() % cache_.size()] = cache_entry;
  }

  return ns;
}

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

#include "src/trace_processor/importers/common/clock_tracker.h"

#include <inttypes.h>

#include <algorithm>
#include <queue>

#include "perfetto/base/logging.h"
#include "perfetto/ext/base/hash.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"

#include "protos/perfetto/common/builtin_clock.pbzero.h"
#include "protos/perfetto/trace/clock_snapshot.pbzero.h"

namespace perfetto {
namespace trace_processor {

using Clock = protos::pbzero::ClockSnapshot::Clock;

ClockTracker::ClockTracker(TraceProcessorContext* ctx)
    : context_(ctx),
      trace_time_clock_id_(protos::pbzero::BUILTIN_CLOCK_BOOTTIME) {}

ClockTracker::~ClockTracker() = default;

void ClockTracker::AddSnapshot(const std::vector<ClockValue>& clocks) {
  const auto snapshot_id = cur_snapshot_id_++;

  // Clear the cache
  cache_.fill({});

  // Compute the fingerprint of the snapshot by hashing all clock ids. This is
  // used by the clock pathfinding logic.
  base::Hash hasher;
  for (const auto& clock : clocks)
    hasher.Update(clock.clock_id);
  const auto snapshot_hash = static_cast<SnapshotHash>(hasher.digest());

  // Add a new entry in each clock's snapshot vector.
  for (const auto& clock : clocks) {
    ClockId clock_id = clock.clock_id;
    ClockDomain& domain = clocks_[clock_id];
    if (domain.snapshots.empty()) {
      if (clock.is_incremental && !ClockIsSeqScoped(clock_id)) {
        PERFETTO_ELOG("Clock sync error: the global clock with id=%" PRIu64
                      " cannot use incremental encoding; this is only "
                      "supported for sequence-scoped clocks.",
                      clock_id);
        context_->storage->IncrementStats(stats::invalid_clock_snapshots);
        return;
      }
      domain.unit_multiplier_ns = clock.unit_multiplier_ns;
      domain.is_incremental = clock.is_incremental;
    } else if (PERFETTO_UNLIKELY(
                   domain.unit_multiplier_ns != clock.unit_multiplier_ns ||
                   domain.is_incremental != clock.is_incremental)) {
      PERFETTO_ELOG("Clock sync error: the clock domain with id=%" PRIu64
                    " (unit=%" PRIu64
                    ", incremental=%d), was previously registered with "
                    "different properties (unit=%" PRIu64 ", incremental=%d).",
                    clock_id, clock.unit_multiplier_ns, clock.is_incremental,
                    domain.unit_multiplier_ns, domain.is_incremental);
      context_->storage->IncrementStats(stats::invalid_clock_snapshots);
      return;
    }
    const int64_t timestamp_ns =
        clock.absolute_timestamp * domain.unit_multiplier_ns;
    domain.last_timestamp_ns = timestamp_ns;

    ClockSnapshots& vect = domain.snapshots[snapshot_hash];
    if (!vect.snapshot_ids.empty() &&
        PERFETTO_UNLIKELY(vect.snapshot_ids.back() == snapshot_id)) {
      PERFETTO_ELOG("Clock sync error: duplicate clock domain with id=%" PRIu64
                    " at snapshot %" PRIu32 ".",
                    clock_id, snapshot_id);
      context_->storage->IncrementStats(stats::invalid_clock_snapshots);
      return;
    }

    // Clock ids in the range [64, 128) are sequence-scoped and must be
    // translated to global ids via SeqScopedClockIdToGlobal() before calling
    // this function.
    PERFETTO_DCHECK(!IsReservedSeqScopedClockId(clock_id));

    // Snapshot IDs must be always monotonic.
    PERFETTO_DCHECK(vect.snapshot_ids.empty() ||
                    vect.snapshot_ids.back() < snapshot_id);

    if (!vect.timestamps_ns.empty() &&
        timestamp_ns < vect.timestamps_ns.back()) {
      // Clock is not monotonic.

      if (clock_id == trace_time_clock_id_) {
        // The trace clock cannot be non-monotonic.
        PERFETTO_ELOG("Clock sync error: the trace clock (id=%" PRIu64
                      ") is not monotonic at snapshot %" PRIu32 ". %" PRId64
                      " not >= %" PRId64 ".",
                      clock_id, snapshot_id, timestamp_ns,
                      vect.timestamps_ns.back());
        context_->storage->IncrementStats(stats::invalid_clock_snapshots);
        return;
      }

      PERFETTO_DLOG("Detected non-monotonic clock with ID %" PRIu64, clock_id);

      // For the other clocks the best thing we can do is mark it as
      // non-monotonic and refuse to use it as a source clock in the resolution
      // graph. We can still use it as a target clock, but not viceversa.
      // The concrete example is the CLOCK_REALTIME going 1h backwards during
      // daylight saving. We can still answer the question "what was the
      // REALTIME timestamp when BOOTTIME was X?" but we can't answer the
      // opposite question because there can be two valid BOOTTIME(s) for the
      // same REALTIME instant because of the 1:many relationship.
      non_monotonic_clocks_.insert(clock_id);

      // Erase all edges from the graph that start from this clock (but keep the
      // ones that end on this clock).
      auto begin = graph_.lower_bound(ClockGraphEdge{clock_id, 0, 0});
      auto end = graph_.lower_bound(ClockGraphEdge{clock_id + 1, 0, 0});
      graph_.erase(begin, end);
    }
    vect.snapshot_ids.emplace_back(snapshot_id);
    vect.timestamps_ns.emplace_back(timestamp_ns);
  }

  // Create graph edges for all the possible tuples of clocks in this snapshot.
  // If the snapshot contains clock a, b, c, d create edges [ab, ac, ad, bc, bd,
  // cd] and the symmetrical ones [ba, ca, da, bc, db, dc].
  // This is to store the information: Clock A is syncable to Clock B via the
  // snapshots of type (hash).
  // Clocks that were previously marked as non-monotonic won't be added as
  // valid sources.
  for (auto it1 = clocks.begin(); it1 != clocks.end(); ++it1) {
    auto it2 = it1;
    ++it2;
    for (; it2 != clocks.end(); ++it2) {
      if (!non_monotonic_clocks_.count(it1->clock_id))
        graph_.emplace(it1->clock_id, it2->clock_id, snapshot_hash);

      if (!non_monotonic_clocks_.count(it2->clock_id))
        graph_.emplace(it2->clock_id, it1->clock_id, snapshot_hash);
    }
  }
}

// Finds the shortest clock resolution path in the graph that allows to
// translate a timestamp from |src| to |target| clocks.
// The return value looks like the following: "If you want to convert a
// timestamp from clock C1 to C2 you need to first convert C1 -> C3 using the
// snapshot hash A, then convert C3 -> C2 via snapshot hash B".
ClockTracker::ClockPath ClockTracker::FindPath(ClockId src, ClockId target) {
  // This is a classic breadth-first search. Each node in the queue holds also
  // the full path to reach that node.
  // We assume the graph is acyclic, if it isn't the ClockPath::kMaxLen will
  // stop the search anyways.
  PERFETTO_CHECK(src != target);
  std::queue<ClockPath> queue;
  queue.emplace(src);

  while (!queue.empty()) {
    ClockPath cur_path = queue.front();
    queue.pop();

    const ClockId cur_clock_id = cur_path.last;
    if (cur_clock_id == target)
      return cur_path;

    if (cur_path.len >= ClockPath::kMaxLen)
      continue;

    // Expore all the adjacent clocks.
    // The lower_bound() below returns an iterator to the first edge that starts
    // on |cur_clock_id|. The edges are sorted by (src, target, hash).
    for (auto it = std::lower_bound(graph_.begin(), graph_.end(),
                                    ClockGraphEdge(cur_clock_id, 0, 0));
         it != graph_.end() && std::get<0>(*it) == cur_clock_id; ++it) {
      ClockId next_clock_id = std::get<1>(*it);
      SnapshotHash hash = std::get<2>(*it);
      queue.push(ClockPath(cur_path, next_clock_id, hash));
    }
  }
  return ClockPath();  // invalid path.
}

base::Optional<int64_t> ClockTracker::ConvertSlowpath(ClockId src_clock_id,
                                                      int64_t src_timestamp,
                                                      ClockId target_clock_id) {
  PERFETTO_DCHECK(!IsReservedSeqScopedClockId(src_clock_id));
  PERFETTO_DCHECK(!IsReservedSeqScopedClockId(target_clock_id));

  context_->storage->IncrementStats(stats::clock_sync_cache_miss);

  ClockPath path = FindPath(src_clock_id, target_clock_id);
  if (!path.valid()) {
    // Too many logs maybe emitted when path is invalid.
    static uint64_t dlog_count = 0;
    if (dlog_count++ < 10) {
      PERFETTO_DLOG("No path from clock %" PRIu64 " to %" PRIu64
                    " at timestamp %" PRId64,
                    src_clock_id, target_clock_id, src_timestamp);
    }
    context_->storage->IncrementStats(stats::clock_sync_failure);
    return base::nullopt;
  }

  // We can cache only single-path resolutions between two clocks.
  // Caching multi-path resolutions is harder because the (src,target) tuple
  // is not enough as a cache key: at any step the |ns| value can yield to a
  // different choice of the next snapshot. Multi-path resolutions don't seem
  // too frequent these days, so we focus only on caching the more frequent
  // one-step resolutions (typically from any clock to the trace clock).
  const bool cacheable = path.len == 1;
  CachedClockPath cache_entry{};

  // Iterate trough the path found and translate timestamps onto the new clock
  // domain on each step, until the target domain is reached.
  ClockDomain* src_domain = GetClock(src_clock_id);
  int64_t ns = src_domain->ToNs(src_timestamp);
  for (uint32_t i = 0; i < path.len; ++i) {
    const ClockGraphEdge edge = path.at(i);
    ClockDomain* cur_clock = GetClock(std::get<0>(edge));
    ClockDomain* next_clock = GetClock(std::get<1>(edge));
    const SnapshotHash hash = std::get<2>(edge);

    // Find the closest timestamp within the snapshots of the source clock.
    const ClockSnapshots& cur_snap = cur_clock->GetSnapshot(hash);
    const auto& ts_vec = cur_snap.timestamps_ns;
    auto it = std::upper_bound(ts_vec.begin(), ts_vec.end(), ns);
    if (it != ts_vec.begin())
      --it;

    // Now lookup the snapshot id that matches the closest timestamp.
    size_t index = static_cast<size_t>(std::distance(ts_vec.begin(), it));
    PERFETTO_DCHECK(index < ts_vec.size());
    PERFETTO_DCHECK(cur_snap.snapshot_ids.size() == ts_vec.size());
    uint32_t snapshot_id = cur_snap.snapshot_ids[index];

    // And use that to retrieve the corresponding time in the next clock domain.
    // The snapshot id must exist in the target clock domain. If it doesn't
    // either the hash logic or the pathfinding logic are bugged.
    // This can also happen if the checks in AddSnapshot fail and we skip part
    // of the snapshot.
    const ClockSnapshots& next_snap = next_clock->GetSnapshot(hash);

    // Using std::lower_bound because snapshot_ids is sorted, so we can do
    // a binary search. std::find would do a linear scan.
    auto next_it = std::lower_bound(next_snap.snapshot_ids.begin(),
                                    next_snap.snapshot_ids.end(), snapshot_id);
    if (next_it == next_snap.snapshot_ids.end() || *next_it != snapshot_id) {
      PERFETTO_DFATAL("Snapshot does not exist in clock domain.");
      continue;
    }
    size_t next_index = static_cast<size_t>(
        std::distance(next_snap.snapshot_ids.begin(), next_it));
    PERFETTO_DCHECK(next_index < next_snap.snapshot_ids.size());
    int64_t next_timestamp_ns = next_snap.timestamps_ns[next_index];

    // The translated timestamp is the relative delta of the source timestamp
    // from the closest snapshot found (ns - *it), plus the timestamp in
    // the new clock domain for the same snapshot id.
    const int64_t adj = next_timestamp_ns - *it;
    ns += adj;

    // On the first iteration, keep track of the bounds for the cache entry.
    // This will allow future Convert() calls to skip the pathfinder logic
    // as long as the query stays within the bound.
    if (cacheable) {
      PERFETTO_DCHECK(i == 0);
      const int64_t kInt64Min = std::numeric_limits<int64_t>::min();
      const int64_t kInt64Max = std::numeric_limits<int64_t>::max();
      cache_entry.min_ts_ns = it == ts_vec.begin() ? kInt64Min : *it;
      auto ubound = it + 1;
      cache_entry.max_ts_ns = ubound == ts_vec.end() ? kInt64Max : *ubound;
      cache_entry.translation_ns = adj;
    }

    // The last clock in the path must be the target clock.
    PERFETTO_DCHECK(i < path.len - 1 || std::get<1>(edge) == target_clock_id);
  }

  if (cacheable) {
    cache_entry.src = src_clock_id;
    cache_entry.src_domain = src_domain;
    cache_entry.target = target_clock_id;
    cache_[rnd_() % cache_.size()] = cache_entry;
  }

  return ns;
}

}  // namespace trace_processor
}  // namespace perfetto
<|endoftext|>


<commit_before>/*
 * This file is a part of Xpiks - cross platform application for
 * keywording and uploading images for microstocks
 * Copyright (C) 2014-2017 Taras Kushnir <kushnirTV@gmail.com>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "spellchecksuggestionmodel.h"
#include <QQmlEngine>
#include <QHash>
#include <QString>
#include "spellsuggestionsitem.h"
#include "../Models/artworkmetadata.h"
#include "spellcheckerservice.h"
#include "../Commands/commandmanager.h"
#include "../Common/flags.h"
#include "../Common/defines.h"
#include "../Common/basickeywordsmodel.h"

namespace SpellCheck {

    std::vector<std::shared_ptr<SpellSuggestionsItem> > combineSuggestionRequests(const SuggestionsVector &items) {
        QHash<QString, SuggestionsVector > dict;

        size_t size = items.size();
        for (size_t i = 0; i < size; ++i) {
            auto &item = items.at(i);
            const QString &word = item->getWord();
            if (!dict.contains(word)) {
                dict.insert(word, SuggestionsVector());
            }

            dict[word].emplace_back(item);
        }

        SuggestionsVector result;
        result.reserve(size);

        QHash<QString, SuggestionsVector >::iterator i = dict.begin();
        QHash<QString, SuggestionsVector >::iterator end = dict.end();
        for (; i != end; ++i) {
            SuggestionsVector &vector = i.value();

            if (vector.size() > 1) {
                result.emplace_back(new CombinedSpellSuggestions(i.key(), vector));
            } else {
                result.emplace_back(vector.front());
            }
        }

        return result;
    }

    QHash<Common::IMetadataOperator *, KeywordsSuggestionsVector > combinedFailedReplacements(const SuggestionsVector &failedReplacements) {
        QHash<Common::IMetadataOperator *, KeywordsSuggestionsVector > candidatesToRemove;
        size_t size = failedReplacements.size();
        candidatesToRemove.reserve((int)size);

        for (size_t i = 0; i < size; ++i) {
            auto &item = failedReplacements.at(i);
            std::shared_ptr<KeywordSpellSuggestions> keywordsItem = std::dynamic_pointer_cast<KeywordSpellSuggestions>(item);

            if (keywordsItem) {
                auto *item = keywordsItem->getMetadataOperator();

                if (keywordsItem->isPotentialDuplicate()) {
                    if (!candidatesToRemove.contains(item)) {
                        candidatesToRemove.insert(item, KeywordsSuggestionsVector());
                    }

                    candidatesToRemove[item].emplace_back(keywordsItem);
                }
            } else {
                std::shared_ptr<CombinedSpellSuggestions> combinedItem = std::dynamic_pointer_cast<CombinedSpellSuggestions>(item);
                if (combinedItem) {
                    auto keywordsItems = combinedItem->getKeywordsDuplicateSuggestions();

                    for (auto &keywordsCombinedItem: keywordsItems) {
                        auto *item = keywordsCombinedItem->getMetadataOperator();

                        if (!candidatesToRemove.contains(item)) {
                            candidatesToRemove.insert(item, KeywordsSuggestionsVector());
                        }

                        candidatesToRemove[item].emplace_back(keywordsCombinedItem);
                    }
                } else {
                    LOG_WARNING << "Unsupported failed suggestion type";
                }
            }
        }

        return candidatesToRemove;
    }

    SpellCheckSuggestionModel::SpellCheckSuggestionModel():
        QAbstractListModel(),
        Common::BaseEntity()
    {
    }

    SpellCheckSuggestionModel::~SpellCheckSuggestionModel() {
    }

    QObject *SpellCheckSuggestionModel::getSuggestionItself(int index) const {
        SpellSuggestionsItem *item = NULL;

        if (0 <= index && index < (int)m_SuggestionsList.size()) {
            item = m_SuggestionsList.at(index).get();
            QQmlEngine::setObjectOwnership(item, QQmlEngine::CppOwnership);
        }

        return item;
    }

    void SpellCheckSuggestionModel::clearModel() {
        beginResetModel();
        m_SuggestionsList.clear();
        m_ItemsPairs.clear();
        endResetModel();
        emit artworksCountChanged();
    }

    void SpellCheckSuggestionModel::submitCorrections() const {
        LOG_DEBUG << "#";
        bool anyChanged = false;

        SuggestionsVector failedItems;

        for (auto &item: m_SuggestionsList) {
            if (item->anyReplacementSelected()) {
                item->replaceToSuggested();

                if (!item->getReplacementSucceeded()) {
                    failedItems.push_back(item);
                } else {
                    anyChanged = true;
                }
            }
        }

        if (processFailedReplacements(failedItems)) {
            anyChanged = true;
        }

        if (anyChanged) {
            for (auto &pair: m_ItemsPairs) {
                auto *item = pair.first;
                item->afterReplaceCallback();
                m_CommandManager->submitItemForSpellCheck(item->getBasicKeywordsModel());
            }
        }

        updateItems();
    }

    void SpellCheckSuggestionModel::resetAllSuggestions() {
        LOG_DEBUG << "#";
        for (auto &item: m_SuggestionsList) {
            item->setReplacementIndex(-1);
        }
    }

    void SpellCheckSuggestionModel::setupModel(Common::IMetadataOperator *item, int index, Common::SuggestionFlags flags) {
        Q_ASSERT(item != NULL);
        LOG_DEBUG << "#";

        std::vector<std::pair<Common::IMetadataOperator *, int> > items;
        items.emplace_back(item, index);
        this->setupModel(items, flags);
    }

    void SpellCheckSuggestionModel::setupModel(std::vector<std::pair<Common::IMetadataOperator *, int> > &items, Common::SuggestionFlags flags) {
        LOG_INFO << "flags =" << (int)flags;
        m_ItemsPairs = std::move(items);

        auto requests = createSuggestionsRequests(flags);

        auto combinedRequests = combineSuggestionRequests(requests);
        LOG_INFO << combinedRequests.size() << "combined request(s)";

        auto executedRequests = setupSuggestions(combinedRequests);
        LOG_INFO << executedRequests.size() << "executed request(s)";

#if defined(CORE_TESTS) || defined(INTEGRATION_TESTS)
        for (auto &executedItem: executedRequests) {
            LOG_INFO << executedItem->toDebugString();
        }
#endif

        beginResetModel();
        m_SuggestionsList.clear();
        m_SuggestionsList = executedRequests;
        endResetModel();

        emit artworksCountChanged();
    }

    SuggestionsVector SpellCheckSuggestionModel::createSuggestionsRequests(Common::SuggestionFlags flags) {
        SuggestionsVector requests;

        using namespace Common;

        if (Common::HasFlag(flags, SuggestionFlags::Keywords)) {
            for (auto &pair: m_ItemsPairs) {
                auto *item = pair.first;
                auto subrequests = item->createKeywordsSuggestionsList();
                for (auto &req: subrequests) { req->setMetadataOperator(item); }
                requests.insert(requests.end(), subrequests.begin(), subrequests.end());
                LOG_DEBUG << subrequests.size() << "keywords requests";
            }
        }

        if (Common::HasFlag(flags, SuggestionFlags::Title)) {
            for (auto &pair: m_ItemsPairs) {
                auto *item = pair.first;
                auto subrequests = item->createTitleSuggestionsList();
                for (auto &req: subrequests) { req->setMetadataOperator(item); }
                requests.insert(requests.end(), subrequests.begin(), subrequests.end());
                LOG_DEBUG << subrequests.size() << "title requests";
            }
        }

        if (Common::HasFlag(flags, SuggestionFlags::Description)) {
            for (auto &pair: m_ItemsPairs) {
                auto *item = pair.first;
                auto subrequests = item->createDescriptionSuggestionsList();
                for (auto &req: subrequests) { req->setMetadataOperator(item); }
                requests.insert(requests.end(), subrequests.begin(), subrequests.end());
                LOG_DEBUG << subrequests.size() << "description requests";
            }
        }

        return requests;
    }

    bool SpellCheckSuggestionModel::processFailedReplacements(const SuggestionsVector &failedReplacements) const {
        LOG_INFO << failedReplacements.size() << "failed items";

        auto candidatesToRemove = combinedFailedReplacements(failedReplacements);

        auto it = candidatesToRemove.begin();
        auto itEnd = candidatesToRemove.end();

        bool anyReplaced = false;
        for (; it != itEnd; ++it) {
            auto *item = it.key();

            if (item->processFailedKeywordReplacements(it.value())) {
                anyReplaced = true;
            }
        }

        return anyReplaced;
    }

    SuggestionsVector SpellCheckSuggestionModel::setupSuggestions(const SuggestionsVector &items) {
        LOG_INFO << items.size() << "item(s)";
        SpellCheckerService *service = m_CommandManager->getSpellCheckerService();
        // another vector for requests with available suggestions
        SuggestionsVector executedRequests;
        executedRequests.reserve(items.size());

        for (auto &item: items) {
            QStringList suggestions = service->suggestCorrections(item->getWord());
            if (!suggestions.isEmpty()) {
                item->setSuggestions(suggestions);
                executedRequests.push_back(item);
            }
        }

        return executedRequests;
    }

    int SpellCheckSuggestionModel::rowCount(const QModelIndex &parent) const {
        Q_UNUSED(parent);
        return (int)m_SuggestionsList.size();
    }

    QVariant SpellCheckSuggestionModel::data(const QModelIndex &index, int role) const {
        int row = index.row();
        if (row < 0 || row >= (int)m_SuggestionsList.size()) { return QVariant(); }

        auto &item = m_SuggestionsList.at(row);

        switch (role) {
        case WordRole:
            return item->getWord();
        case ReplacementIndexRole:
            return item->getReplacementIndex();
        case ReplacementOriginRole:
            return item->getReplacementOrigin();
        default:
            return QVariant();
        }
    }

    QHash<int, QByteArray> SpellCheckSuggestionModel::roleNames() const {
        QHash<int, QByteArray> roles;
        roles[WordRole] = "word";
        roles[ReplacementIndexRole] = "replacementindex";
        roles[ReplacementOriginRole] = "replacementorigin";
        return roles;
    }

    void SpellCheckSuggestionModel::updateItems() const {
        QVector<int> indices;
        indices.reserve((int)m_ItemsPairs.size());

        for (auto &pair: m_ItemsPairs) {
            int index = pair.second;
            if (index != -1) {
                indices.push_back(index);
            }
        }

        m_CommandManager->updateArtworksAtIndices(indices);
    }
}
<commit_msg>Fix for multireplace test<commit_after>/*
 * This file is a part of Xpiks - cross platform application for
 * keywording and uploading images for microstocks
 * Copyright (C) 2014-2017 Taras Kushnir <kushnirTV@gmail.com>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "spellchecksuggestionmodel.h"
#include <QQmlEngine>
#include <QHash>
#include <QString>
#include "spellsuggestionsitem.h"
#include "../Models/artworkmetadata.h"
#include "spellcheckerservice.h"
#include "../Commands/commandmanager.h"
#include "../Common/flags.h"
#include "../Common/defines.h"
#include "../Common/basickeywordsmodel.h"

namespace SpellCheck {

    std::vector<std::shared_ptr<SpellSuggestionsItem> > combineSuggestionRequests(const SuggestionsVector &items) {
        QHash<QString, SuggestionsVector > dict;

        size_t size = items.size();
        for (size_t i = 0; i < size; ++i) {
            auto &item = items.at(i);
            const QString &word = item->getWord();
            if (!dict.contains(word)) {
                dict.insert(word, SuggestionsVector());
            }

            dict[word].emplace_back(item);
        }

        SuggestionsVector result;
        result.reserve(size);

        QHash<QString, SuggestionsVector >::iterator i = dict.begin();
        QHash<QString, SuggestionsVector >::iterator end = dict.end();
        for (; i != end; ++i) {
            SuggestionsVector &vector = i.value();

            if (vector.size() > 1) {
                result.emplace_back(new CombinedSpellSuggestions(i.key(), vector));
            } else {
                result.emplace_back(vector.front());
            }
        }

        return result;
    }

    QHash<Common::IMetadataOperator *, KeywordsSuggestionsVector > combinedFailedReplacements(const SuggestionsVector &failedReplacements) {
        QHash<Common::IMetadataOperator *, KeywordsSuggestionsVector > candidatesToRemove;
        size_t size = failedReplacements.size();
        candidatesToRemove.reserve((int)size);

        for (size_t i = 0; i < size; ++i) {
            auto &item = failedReplacements.at(i);
            std::shared_ptr<KeywordSpellSuggestions> keywordsItem = std::dynamic_pointer_cast<KeywordSpellSuggestions>(item);

            if (keywordsItem) {
                auto *item = keywordsItem->getMetadataOperator();

                if (keywordsItem->isPotentialDuplicate()) {
                    if (!candidatesToRemove.contains(item)) {
                        candidatesToRemove.insert(item, KeywordsSuggestionsVector());
                    }

                    candidatesToRemove[item].emplace_back(keywordsItem);
                }
            } else {
                std::shared_ptr<CombinedSpellSuggestions> combinedItem = std::dynamic_pointer_cast<CombinedSpellSuggestions>(item);
                if (combinedItem) {
                    auto keywordsItems = combinedItem->getKeywordsDuplicateSuggestions();

                    for (auto &keywordsCombinedItem: keywordsItems) {
                        auto *item = keywordsCombinedItem->getMetadataOperator();

                        if (!candidatesToRemove.contains(item)) {
                            candidatesToRemove.insert(item, KeywordsSuggestionsVector());
                        }

                        candidatesToRemove[item].emplace_back(keywordsCombinedItem);
                    }
                } else {
                    LOG_WARNING << "Unsupported failed suggestion type";
                }
            }
        }

        return candidatesToRemove;
    }

    SpellCheckSuggestionModel::SpellCheckSuggestionModel():
        QAbstractListModel(),
        Common::BaseEntity()
    {
    }

    SpellCheckSuggestionModel::~SpellCheckSuggestionModel() {
    }

    QObject *SpellCheckSuggestionModel::getSuggestionItself(int index) const {
        SpellSuggestionsItem *item = NULL;

        if (0 <= index && index < (int)m_SuggestionsList.size()) {
            item = m_SuggestionsList.at(index).get();
            QQmlEngine::setObjectOwnership(item, QQmlEngine::CppOwnership);
        }

        return item;
    }

    void SpellCheckSuggestionModel::clearModel() {
        beginResetModel();
        m_SuggestionsList.clear();
        m_ItemsPairs.clear();
        endResetModel();
        emit artworksCountChanged();
    }

    void SpellCheckSuggestionModel::submitCorrections() const {
        LOG_DEBUG << "#";
        bool anyChanged = false;

        SuggestionsVector failedItems;

        for (auto &item: m_SuggestionsList) {
            if (item->anyReplacementSelected()) {
                item->replaceToSuggested();

                if (!item->getReplacementSucceeded()) {
                    failedItems.push_back(item);
                } else {
                    anyChanged = true;
                }
            }
        }

        if (processFailedReplacements(failedItems)) {
            anyChanged = true;
        }

        if (anyChanged) {
             QVector<Common::BasicKeywordsModel *> itemsToSubmit;
             itemsToSubmit.reserve((int)m_ItemsPairs.size());

             for (auto &pair: m_ItemsPairs) {
                 auto *item = pair.first;
                 item->afterReplaceCallback();
                 itemsToSubmit.append(item->getBasicKeywordsModel());
             }

             m_CommandManager->submitForSpellCheck(itemsToSubmit);
        }

        updateItems();
    }

    void SpellCheckSuggestionModel::resetAllSuggestions() {
        LOG_DEBUG << "#";
        for (auto &item: m_SuggestionsList) {
            item->setReplacementIndex(-1);
        }
    }

    void SpellCheckSuggestionModel::setupModel(Common::IMetadataOperator *item, int index, Common::SuggestionFlags flags) {
        Q_ASSERT(item != NULL);
        LOG_DEBUG << "#";

        std::vector<std::pair<Common::IMetadataOperator *, int> > items;
        items.emplace_back(item, index);
        this->setupModel(items, flags);
    }

    void SpellCheckSuggestionModel::setupModel(std::vector<std::pair<Common::IMetadataOperator *, int> > &items, Common::SuggestionFlags flags) {
        LOG_INFO << "flags =" << (int)flags;
        m_ItemsPairs = std::move(items);

        auto requests = createSuggestionsRequests(flags);

        auto combinedRequests = combineSuggestionRequests(requests);
        LOG_INFO << combinedRequests.size() << "combined request(s)";

        auto executedRequests = setupSuggestions(combinedRequests);
        LOG_INFO << executedRequests.size() << "executed request(s)";

#if defined(CORE_TESTS) || defined(INTEGRATION_TESTS)
        for (auto &executedItem: executedRequests) {
            LOG_INFO << executedItem->toDebugString();
        }
#endif

        beginResetModel();
        m_SuggestionsList.clear();
        m_SuggestionsList = executedRequests;
        endResetModel();

        emit artworksCountChanged();
    }

    SuggestionsVector SpellCheckSuggestionModel::createSuggestionsRequests(Common::SuggestionFlags flags) {
        SuggestionsVector requests;

        using namespace Common;

        if (Common::HasFlag(flags, SuggestionFlags::Keywords)) {
            for (auto &pair: m_ItemsPairs) {
                auto *item = pair.first;
                auto subrequests = item->createKeywordsSuggestionsList();
                for (auto &req: subrequests) { req->setMetadataOperator(item); }
                requests.insert(requests.end(), subrequests.begin(), subrequests.end());
                LOG_DEBUG << subrequests.size() << "keywords requests";
            }
        }

        if (Common::HasFlag(flags, SuggestionFlags::Title)) {
            for (auto &pair: m_ItemsPairs) {
                auto *item = pair.first;
                auto subrequests = item->createTitleSuggestionsList();
                for (auto &req: subrequests) { req->setMetadataOperator(item); }
                requests.insert(requests.end(), subrequests.begin(), subrequests.end());
                LOG_DEBUG << subrequests.size() << "title requests";
            }
        }

        if (Common::HasFlag(flags, SuggestionFlags::Description)) {
            for (auto &pair: m_ItemsPairs) {
                auto *item = pair.first;
                auto subrequests = item->createDescriptionSuggestionsList();
                for (auto &req: subrequests) { req->setMetadataOperator(item); }
                requests.insert(requests.end(), subrequests.begin(), subrequests.end());
                LOG_DEBUG << subrequests.size() << "description requests";
            }
        }

        return requests;
    }

    bool SpellCheckSuggestionModel::processFailedReplacements(const SuggestionsVector &failedReplacements) const {
        LOG_INFO << failedReplacements.size() << "failed items";

        auto candidatesToRemove = combinedFailedReplacements(failedReplacements);

        auto it = candidatesToRemove.begin();
        auto itEnd = candidatesToRemove.end();

        bool anyReplaced = false;
        for (; it != itEnd; ++it) {
            auto *item = it.key();

            if (item->processFailedKeywordReplacements(it.value())) {
                anyReplaced = true;
            }
        }

        return anyReplaced;
    }

    SuggestionsVector SpellCheckSuggestionModel::setupSuggestions(const SuggestionsVector &items) {
        LOG_INFO << items.size() << "item(s)";
        SpellCheckerService *service = m_CommandManager->getSpellCheckerService();
        // another vector for requests with available suggestions
        SuggestionsVector executedRequests;
        executedRequests.reserve(items.size());

        for (auto &item: items) {
            QStringList suggestions = service->suggestCorrections(item->getWord());
            if (!suggestions.isEmpty()) {
                item->setSuggestions(suggestions);
                executedRequests.push_back(item);
            }
        }

        return executedRequests;
    }

    int SpellCheckSuggestionModel::rowCount(const QModelIndex &parent) const {
        Q_UNUSED(parent);
        return (int)m_SuggestionsList.size();
    }

    QVariant SpellCheckSuggestionModel::data(const QModelIndex &index, int role) const {
        int row = index.row();
        if (row < 0 || row >= (int)m_SuggestionsList.size()) { return QVariant(); }

        auto &item = m_SuggestionsList.at(row);

        switch (role) {
        case WordRole:
            return item->getWord();
        case ReplacementIndexRole:
            return item->getReplacementIndex();
        case ReplacementOriginRole:
            return item->getReplacementOrigin();
        default:
            return QVariant();
        }
    }

    QHash<int, QByteArray> SpellCheckSuggestionModel::roleNames() const {
        QHash<int, QByteArray> roles;
        roles[WordRole] = "word";
        roles[ReplacementIndexRole] = "replacementindex";
        roles[ReplacementOriginRole] = "replacementorigin";
        return roles;
    }

    void SpellCheckSuggestionModel::updateItems() const {
        QVector<int> indices;
        indices.reserve((int)m_ItemsPairs.size());

        for (auto &pair: m_ItemsPairs) {
            int index = pair.second;
            if (index != -1) {
                indices.push_back(index);
            }
        }

        m_CommandManager->updateArtworksAtIndices(indices);
    }
}
<|endoftext|>


<commit_before>//   Copyright (c) 2014-2016 SSPA Sweden AB

#pragma once

#include <vector>
#include <map>
#include <utility>
#include <string>
#include <memory>

#include "PysimTypes.hpp"

struct Dptr;

class StoreHandler{
public:
    StoreHandler();
    virtual ~StoreHandler();

    void doStoreStep(double time);

    //Store handling
    const std::vector<double>& getStoreVector(char* name);
    void fillWithStore(char* name, double* p,int rows, int columns);
    void fillWithTime(double* p);
    int getStoreSize();
    int getStoreColumns(char* name);
    void setStoreInterval(double interval);
    std::vector<std::string> getStoreNames();
    void store_scalar(char* name, double* pointer);
    void store_vector(char* name, pysim::vector* pointer);

protected:

private:
    Dptr* d_ptr;

};

<commit_msg>removing unused imports<commit_after>//   Copyright (c) 2014-2016 SSPA Sweden AB
#pragma once

#include <vector>
#include <map>
#include <string>

#include "PysimTypes.hpp"

struct Dptr;

class StoreHandler{
public:
    StoreHandler();
    virtual ~StoreHandler();

    void doStoreStep(double time);

    //Store handling
    const std::vector<double>& getStoreVector(char* name);
    void fillWithStore(char* name, double* p,int rows, int columns);
    void fillWithTime(double* p);
    int getStoreSize();
    int getStoreColumns(char* name);
    void setStoreInterval(double interval);
    std::vector<std::string> getStoreNames();
    void store_scalar(char* name, double* pointer);
    void store_vector(char* name, pysim::vector* pointer);

protected:

private:
    Dptr* d_ptr;

};

<|endoftext|>


<commit_before>/*=========================================================================

 Program:   ORFEO Toolbox
 Language:  C++
 Date:      $Date$
 Version:   $Revision$

 Copyright (c) Centre National d'Etudes Spatiales. All rights reserved.
 See OTBCopyright.txt for details.

 This software is distributed WITHOUT ANY WARRANTY; without even
 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 PURPOSE.  See the above copyright notices for more information.

 =========================================================================*/

#include "otbVectorImage.h"
#include "itkMacro.h"
#include <iostream>
#include "otbImageFileReader.h"
#include "otbImageFileWriter.h"
#include "otbMultiChannelExtractROI.h"

int otbImageFileWriterWithExtendedOptionBox(int argc, char* argv[])
{
  // Verify the number of parameters in the command line
  const std::string inputFilename = argv[1];
  const std::string outputFilename = argv[2];

  const unsigned int startx = atoi(argv[3]);
  const unsigned int starty = atoi(argv[4]);
  const unsigned int sizex = atoi(argv[5]);
  const unsigned int sizey = atoi(argv[6]);
  const std::string separator = ":";

  typedef float InputPixelType;
  typedef float OutputPixelType;

  typedef otb::VectorImage<InputPixelType, 2> InputImageType;

  typedef typename InputImageType::PixelType   PixelType;

  typedef otb::MultiChannelExtractROI<InputImageType::InternalPixelType,
                                      InputImageType::InternalPixelType> ExtractROIFilterType;

  typedef otb::ImageFileReader<InputImageType> ReaderType;
  typedef otb::ImageFileWriter<InputImageType> WriterType;

  typedef itk::ImageRegionIterator< InputImageType >       IteratorType;
  typedef itk::ImageRegionConstIterator< InputImageType >  ConstIteratorType;

  ReaderType::Pointer reader = ReaderType::New();
  ExtractROIFilterType::Pointer extractROIFilter = ExtractROIFilterType::New();
  WriterType::Pointer writer = WriterType::New();

  reader->SetFileName(inputFilename);

  //Build output filename with extended box
  std::ostringstream outputFilenameExtended;
  outputFilenameExtended
                         << outputFilename
                         << "?&box=" << startx << separator
                         << starty << separator
                         << sizex << separator
                         << sizey
                        ;

  std::cout << "Output image with user defined path " << outputFilenameExtended.str() << std::endl;

  writer->SetFileName(outputFilenameExtended.str());
  writer->SetInput(reader->GetOutput());

  extractROIFilter->SetStartX(startx);
  extractROIFilter->SetStartY(starty);
  extractROIFilter->SetSizeX(sizex);
  extractROIFilter->SetSizeY(sizey);
  extractROIFilter->SetInput(reader->GetOutput());

  writer->Update();
  extractROIFilter->Update();

  ReaderType::Pointer reader2 = ReaderType::New();
  reader2->SetFileName(outputFilename);

  reader2->Update();

  typename InputImageType::ConstPointer readImage = reader2->GetOutput();
  typename InputImageType::ConstPointer extractImage = extractROIFilter->GetOutput();

  ConstIteratorType ritr( readImage, readImage->GetLargestPossibleRegion() );
  ConstIteratorType extractitr( extractImage, extractImage->GetLargestPossibleRegion() );

  ritr.GoToBegin();
  extractitr.GoToBegin();

  std::cout << "Comparing the pixel values.. :" << std::endl;

  while( !ritr.IsAtEnd() &&  !extractitr.IsAtEnd() )
    {
    if( ritr.Get() != extractitr.Get() )
      {
      std::cerr << "Pixel comparison failed at index = " << ritr.GetIndex() << std::endl;
      std::cerr << "Expected pixel value " << extractitr.Get() << std::endl;
      std::cerr << "Read Image pixel value " << ritr.Get() << std::endl;
      return EXIT_FAILURE;
      }
    ++extractitr;
    ++ritr;
    }

  std::cout << std::endl;
  std::cout << "Test PASSED !" << std::endl;

  return EXIT_SUCCESS;
}
<commit_msg>STYLE<commit_after>/*=========================================================================

 Program:   ORFEO Toolbox
 Language:  C++
 Date:      $Date$
 Version:   $Revision$

 Copyright (c) Centre National d'Etudes Spatiales. All rights reserved.
 See OTBCopyright.txt for details.

 This software is distributed WITHOUT ANY WARRANTY; without even
 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 PURPOSE.  See the above copyright notices for more information.

 =========================================================================*/

#include "otbVectorImage.h"
#include "itkMacro.h"
#include <iostream>
#include "otbImageFileReader.h"
#include "otbImageFileWriter.h"
#include "otbMultiChannelExtractROI.h"

int otbImageFileWriterWithExtendedOptionBox(int argc, char* argv[])
{
  // Verify the number of parameters in the command line
  const std::string inputFilename = argv[1];
  const std::string outputFilename = argv[2];

  const unsigned int startx = atoi(argv[3]);
  const unsigned int starty = atoi(argv[4]);
  const unsigned int sizex = atoi(argv[5]);
  const unsigned int sizey = atoi(argv[6]);
  const std::string separator = ":";

  typedef float InputPixelType;
  typedef float OutputPixelType;

  typedef otb::VectorImage<InputPixelType, 2> InputImageType;

  typedef typename InputImageType::PixelType   PixelType;

  typedef otb::MultiChannelExtractROI<InputImageType::InternalPixelType,
                                      InputImageType::InternalPixelType> ExtractROIFilterType;

  typedef otb::ImageFileReader<InputImageType> ReaderType;
  typedef otb::ImageFileWriter<InputImageType> WriterType;

  typedef itk::ImageRegionIterator< InputImageType >       IteratorType;
  typedef itk::ImageRegionConstIterator< InputImageType >  ConstIteratorType;

  ReaderType::Pointer reader = ReaderType::New();
  ExtractROIFilterType::Pointer extractROIFilter = ExtractROIFilterType::New();
  WriterType::Pointer writer = WriterType::New();

  reader->SetFileName(inputFilename);

  //Build output filename with extended box
  std::ostringstream outputFilenameExtended;
  outputFilenameExtended
                         << outputFilename
                         << "?&box=" << startx << separator
                         << starty << separator
                         << sizex << separator
                         << sizey
                       ;

  std::cout << "Output image with user defined path " << outputFilenameExtended.str() << std::endl;

  writer->SetFileName(outputFilenameExtended.str());
  writer->SetInput(reader->GetOutput());

  extractROIFilter->SetStartX(startx);
  extractROIFilter->SetStartY(starty);
  extractROIFilter->SetSizeX(sizex);
  extractROIFilter->SetSizeY(sizey);
  extractROIFilter->SetInput(reader->GetOutput());

  writer->Update();
  extractROIFilter->Update();

  ReaderType::Pointer reader2 = ReaderType::New();
  reader2->SetFileName(outputFilename);

  reader2->Update();

  typename InputImageType::ConstPointer readImage = reader2->GetOutput();
  typename InputImageType::ConstPointer extractImage = extractROIFilter->GetOutput();

  ConstIteratorType ritr( readImage, readImage->GetLargestPossibleRegion() );
  ConstIteratorType extractitr( extractImage, extractImage->GetLargestPossibleRegion() );

  ritr.GoToBegin();
  extractitr.GoToBegin();

  std::cout << "Comparing the pixel values.. :" << std::endl;

  while( !ritr.IsAtEnd() &&  !extractitr.IsAtEnd() )
    {
    if( ritr.Get() != extractitr.Get() )
      {
      std::cerr << "Pixel comparison failed at index = " << ritr.GetIndex() << std::endl;
      std::cerr << "Expected pixel value " << extractitr.Get() << std::endl;
      std::cerr << "Read Image pixel value " << ritr.Get() << std::endl;
      return EXIT_FAILURE;
      }
    ++extractitr;
    ++ritr;
    }

  std::cout << std::endl;
  std::cout << "Test PASSED !" << std::endl;

  return EXIT_SUCCESS;
}
<|endoftext|>


<commit_before>/*M///////////////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2015, Youssef Kashef
// Copyright (c) 2015, ELM Library Project
// 3-clause BSD License
//
//M*/
#include "elm/layers/imagegradient.h"

#include "gtest/gtest.h"

#include <memory>

#include "elm/core/exception.h"
#include "elm/core/inputname.h"
#include "elm/core/layerconfig.h"
#include "elm/core/signal.h"
#include "elm/ts/layer_assertions.h"
#include "elm/ts/mat_assertions.h"
#include "elm/core/debug_utils.h"

using namespace std;
using namespace cv;
using namespace elm;

namespace {

ELM_INSTANTIATE_LAYER_TYPED_TEST_CASE_P(ImageGradient);

const std::string NAME_IN      = "in";
const std::string NAME_GRAD    = "g";

class ImageGradientTest : public ::testing::Test
{
protected:
    virtual void SetUp() {

        to_.reset(new ImageGradient());

        config_ = LayerConfig();

        // params
        PTree params;
        config_.Params(params);

        // IO
        io_ = LayerIONames();
        io_.Input(ImageGradient::KEY_INPUT_STIMULUS, NAME_IN);
        io_.Output(ImageGradient::KEY_OUTPUT_RESPONSE, NAME_GRAD);

        to_.reset(new ImageGradient(config_));
        to_->IONames(io_);
    }

    shared_ptr<base_Layer> to_; ///< test object
    LayerConfig config_;        ///< default config for tests
    LayerIONames io_;            ///< default I/O for tests
};

TEST_F(ImageGradientTest, Reset_EmptyConfig)
{
    EXPECT_NO_THROW(to_->Reset(LayerConfig())) << "All params are optional, no?";
}

TEST_F(ImageGradientTest, Response_exists)
{
    Mat1f in(10, 10, 1.f);

    Signal sig;
    sig.Append(NAME_IN, in);

    to_->Activate(sig);

    EXPECT_FALSE(sig.Exists(NAME_GRAD));

    to_->Response(sig);

    EXPECT_TRUE(sig.Exists(NAME_GRAD));
}

TEST_F(ImageGradientTest, Response_dims)
{
    const int R=10;
    const int C=10;

    for(int r=2; r<R; r++) {

        for(int c=2; c<C; c++) {

            Mat1f in(r, c, 1.f);

            Signal sig;
            sig.Append(NAME_IN, in);

            to_->Activate(sig);
            to_->Response(sig);

            Mat1f gradient = sig.MostRecentMat1f(NAME_GRAD);

            EXPECT_EQ(r, gradient.rows);
            EXPECT_EQ(c*2, gradient.cols);
        }
    }
}

TEST_F(ImageGradientTest, Invalid_input)
{
    for(int r=0; r<2; r++) {

        for(int c=0; c<2; c++) {

            Mat1f in(r, c, 1.f);

            Signal sig;
            sig.Append(NAME_IN, in);

            EXPECT_THROW(to_->Activate(sig), ExceptionBadDims);
        }
    }
}

} // annonymous namespace for test cases and fixtures

<commit_msg>test response for image gradient layer<commit_after>/*M///////////////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2015, Youssef Kashef
// Copyright (c) 2015, ELM Library Project
// 3-clause BSD License
//
//M*/
#include "elm/layers/imagegradient.h"

#include "gtest/gtest.h"

#include <memory>

#include "elm/core/exception.h"
#include "elm/core/inputname.h"
#include "elm/core/layerconfig.h"
#include "elm/core/signal.h"
#include "elm/ts/layer_assertions.h"
#include "elm/ts/mat_assertions.h"

using namespace std;
using namespace cv;
using namespace elm;

namespace {

ELM_INSTANTIATE_LAYER_TYPED_TEST_CASE_P(ImageGradient);

const std::string NAME_IN      = "in";
const std::string NAME_GRAD    = "g";

class ImageGradientTest : public ::testing::Test
{
protected:
    virtual void SetUp() {

        to_.reset(new ImageGradient());

        config_ = LayerConfig();

        // params
        PTree params;
        config_.Params(params);

        // IO
        io_ = LayerIONames();
        io_.Input(ImageGradient::KEY_INPUT_STIMULUS, NAME_IN);
        io_.Output(ImageGradient::KEY_OUTPUT_RESPONSE, NAME_GRAD);

        to_.reset(new ImageGradient(config_));
        to_->IONames(io_);
    }

    shared_ptr<base_Layer> to_; ///< test object
    LayerConfig config_;        ///< default config for tests
    LayerIONames io_;            ///< default I/O for tests
};

TEST_F(ImageGradientTest, Reset_EmptyConfig)
{
    EXPECT_NO_THROW(to_->Reset(LayerConfig())) << "All params are optional, no?";
}

TEST_F(ImageGradientTest, Response_exists)
{
    Mat1f in(10, 10, 1.f);

    Signal sig;
    sig.Append(NAME_IN, in);

    to_->Activate(sig);

    EXPECT_FALSE(sig.Exists(NAME_GRAD));

    to_->Response(sig);

    EXPECT_TRUE(sig.Exists(NAME_GRAD));
}

TEST_F(ImageGradientTest, Response_dims)
{
    const int R=10;
    const int C=10;

    for(int r=2; r<R; r++) {

        for(int c=2; c<C; c++) {

            Mat1f in(r, c, 1.f);

            Signal sig;
            sig.Append(NAME_IN, in);

            to_->Activate(sig);
            to_->Response(sig);

            Mat1f gradient = sig.MostRecentMat1f(NAME_GRAD);

            EXPECT_EQ(r, gradient.rows);
            EXPECT_EQ(c*2, gradient.cols);
        }
    }
}

TEST_F(ImageGradientTest, Invalid_input)
{
    for(int r=0; r<2; r++) {

        for(int c=0; c<2; c++) {

            Mat1f in(r, c, 1.f);

            Signal sig;
            sig.Append(NAME_IN, in);

            EXPECT_THROW(to_->Activate(sig), ExceptionBadDims);
        }
    }
}

TEST_F(ImageGradientTest, Gradient)
{
    const int ROWS=3;
    const int COLS=2;

    float data1[ROWS*COLS] = {1.1f,  1.2f,
                              2.3f,  2.2f,
                              -3.1f, 0.f,
                             };
    Mat1f in = Mat1f(ROWS, COLS, data1).clone();

    Signal sig;
    sig.Append(NAME_IN, in);

    to_->Activate(sig);
    to_->Response(sig);

    Mat2f gradient = static_cast<Mat2f>(sig.MostRecentMat1f(NAME_GRAD));

    EXPECT_EQ(ROWS, gradient.rows);
    EXPECT_EQ(COLS, gradient.cols);

    VecMat1f components;
    cv::split(gradient, components);

    Mat1f grad_x = components[0];
    EXPECT_FLOAT_EQ(0.1f, grad_x(0, 0));
    EXPECT_NEAR(-0.1f, grad_x(1, 0), 1e-5);
    EXPECT_FLOAT_EQ(-in(2, 0), grad_x(2, 0));
    EXPECT_MAT_EQ(Mat1f::zeros(ROWS, 1), grad_x.col(COLS-1));

    Mat1f grad_y = components[1];
    EXPECT_FLOAT_EQ(1.2f, grad_y(0, 0));
    EXPECT_FLOAT_EQ(1.0f, grad_y(0, 1));
    EXPECT_FLOAT_EQ(-3.1f-2.3f, grad_y(1, 0));
    EXPECT_NEAR(-2.2f, grad_y(1, 1), 1e-5);
    EXPECT_MAT_EQ(Mat1f::zeros(1, COLS), grad_y.row(ROWS-1));
}

TEST_F(ImageGradientTest, Gradient_nan)
{
    const int ROWS=3;
    const int COLS=2;

    const float NAN_VALUE=std::numeric_limits<float>::quiet_NaN();

    float data1[ROWS*COLS] = {1.1f,  NAN_VALUE,
                              2.3f,  NAN_VALUE,
                              -3.1f, 0.f,
                             };
    Mat1f in = Mat1f(ROWS, COLS, data1).clone();

    Signal sig;
    sig.Append(NAME_IN, in);

    to_->Activate(sig);
    to_->Response(sig);

    Mat2f gradient = static_cast<Mat2f>(sig.MostRecentMat1f(NAME_GRAD));

    EXPECT_EQ(ROWS, gradient.rows);
    EXPECT_EQ(COLS, gradient.cols);

    VecMat1f components;
    cv::split(gradient, components);

    Mat1f grad_x = components[0];

    EXPECT_NE(grad_x(0, 0), grad_x(0, 0));
    EXPECT_NE(grad_x(1, 0), grad_x(1, 0));
    EXPECT_FLOAT_EQ(-in(2, 0), grad_x(2, 0));
    EXPECT_MAT_EQ(Mat1f::zeros(ROWS, 1), grad_x.col(COLS-1));

    Mat1f grad_y = components[1];

    EXPECT_FLOAT_EQ(1.2f, grad_y(0, 0));
    EXPECT_NE(grad_y(0, 1), grad_y(0, 1));
    EXPECT_FLOAT_EQ(-3.1f-2.3f, grad_y(1, 0));
    EXPECT_NE(-grad_y(1, 1), grad_y(1, 1));
    EXPECT_MAT_EQ(Mat1f::zeros(1, COLS), grad_y.row(ROWS-1));
}

} // annonymous namespace for test cases and fixtures

<|endoftext|>