Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e3b92ab6 authored by Fabian Kozynski's avatar Fabian Kozynski Committed by Android (Google) Code Review
Browse files

Merge "Revert "support work chain in pulled atoms""

parents 763707ee 9c7b1319
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -59,7 +59,7 @@ bool StatsCompanionServicePuller::PullInternal(vector<shared_ptr<LogEvent> >* da
        }
        data->clear();
        for (const StatsLogEventWrapper& it : returned_value) {
            LogEvent::createLogEvents(it, *data);
            data->push_back(make_shared<LogEvent>(it));
        }
        VLOG("StatsCompanionServicePuller::pull succeeded for %d", mTagId);
        return true;
+3 −4
Original line number Diff line number Diff line
@@ -32,10 +32,9 @@ using std::lock_guard;
sp<UidMap> StatsPuller::mUidMap = nullptr;
void StatsPuller::SetUidMap(const sp<UidMap>& uidMap) { mUidMap = uidMap; }

// ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
StatsPuller::StatsPuller(const int tagId)
    : mTagId(tagId) {
    // Pullers can cause significant impact to system health and battery.
    // So that we don't pull too frequently.
    mCoolDownNs = StatsPullerManager::kAllPullAtomInfo.find(tagId)->second.coolDownNs;
    VLOG("Puller for tag %d created. Cooldown set to %lld", mTagId, (long long)mCoolDownNs);
}
@@ -65,7 +64,7 @@ bool StatsPuller::Pull(const int64_t elapsedTimeNs, std::vector<std::shared_ptr<
        data->setLogdWallClockTimestampNs(wallClockTimeNs);
    }
    if (ret && mCachedData.size() > 0) {
        mapAndMergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId);
      mergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId);
      (*data) = mCachedData;
    }
    StatsdStats::getInstance().notePullDelay(mTagId, getElapsedRealtimeNs() - elapsedTimeNs);
+66 −29
Original line number Diff line number Diff line
@@ -54,42 +54,52 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
        // wifi_bytes_transfer
        {android::util::WIFI_BYTES_TRANSFER,
         {{2, 3, 4, 5},
          {},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER)}},
        // wifi_bytes_transfer_by_fg_bg
        {android::util::WIFI_BYTES_TRANSFER_BY_FG_BG,
         {{3, 4, 5, 6},
          {2},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}},
        // mobile_bytes_transfer
        {android::util::MOBILE_BYTES_TRANSFER,
         {{2, 3, 4, 5},
          {},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}},
        // mobile_bytes_transfer_by_fg_bg
        {android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG,
         {{3, 4, 5, 6},
          {2},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}},
        // bluetooth_bytes_transfer
        {android::util::BLUETOOTH_BYTES_TRANSFER,
         {{2, 3},
          {},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}},
        // kernel_wakelock
        {android::util::KERNEL_WAKELOCK,
         {{}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
         {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
        // subsystem_sleep_state
        {android::util::SUBSYSTEM_SLEEP_STATE,
         {{}, 1 * NS_PER_SEC, new SubsystemSleepStatePuller()}},
         {{}, {}, 1 * NS_PER_SEC, new SubsystemSleepStatePuller()}},
        // on_device_power_measurement
        {android::util::ON_DEVICE_POWER_MEASUREMENT, {{}, 1 * NS_PER_SEC, new PowerStatsPuller()}},
        {android::util::ON_DEVICE_POWER_MEASUREMENT,
         {{}, {}, 1 * NS_PER_SEC, new PowerStatsPuller()}},
        // cpu_time_per_freq
        {android::util::CPU_TIME_PER_FREQ,
         {{3}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}},
         {{3},
          {2},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}},
        // cpu_time_per_uid
        {android::util::CPU_TIME_PER_UID,
         {{2, 3},
          {},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}},
        // cpu_time_per_uid_freq
@@ -97,135 +107,162 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
        {android::util::CPU_TIME_PER_UID_FREQ,
         {{4},
          {2, 3},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}},
        // cpu_active_time
        // the throttling is 3sec, handled in
        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
        {android::util::CPU_ACTIVE_TIME,
         {{2}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}},
         {{2},
          {},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}},
        // cpu_cluster_time
        // the throttling is 3sec, handled in
        // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
        {android::util::CPU_CLUSTER_TIME,
         {{3}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}},
         {{3},
          {2},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}},
        // wifi_activity_energy_info
        {android::util::WIFI_ACTIVITY_INFO,
         {{}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}},
         {{},
          {},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}},
        // modem_activity_info
        {android::util::MODEM_ACTIVITY_INFO,
         {{}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}},
         {{},
          {},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}},
        // bluetooth_activity_info
        {android::util::BLUETOOTH_ACTIVITY_INFO,
         {{},
          {},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
        // system_elapsed_realtime
        {android::util::SYSTEM_ELAPSED_REALTIME,
         {{},
          {},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME)}},
        // system_uptime
        {android::util::SYSTEM_UPTIME,
         {{}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
         {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
        // remaining_battery_capacity
        {android::util::REMAINING_BATTERY_CAPACITY,
         {{},
          {},
          1 * NS_PER_SEC,
          new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}},
        // full_battery_capacity
        {android::util::FULL_BATTERY_CAPACITY,
         {{},
          {},
          1 * NS_PER_SEC,
          new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}},
        // battery_voltage
        {android::util::BATTERY_VOLTAGE,
         {{}, 1 * NS_PER_SEC, new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}},
         // battery_level
         {{}, {}, 1 * NS_PER_SEC, new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}},
         // battery_voltage
        {android::util::BATTERY_LEVEL,
         {{}, 1 * NS_PER_SEC, new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}},
         {{}, {}, 1 * NS_PER_SEC, new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}},
        // process_memory_state
        {android::util::PROCESS_MEMORY_STATE,
         {{4, 5, 6, 7, 8, 9},
          {2, 3, 10},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}},
        // native_process_memory_state
        {android::util::NATIVE_PROCESS_MEMORY_STATE,
         {{3, 4, 5, 6},
          {2, 7},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::NATIVE_PROCESS_MEMORY_STATE)}},
        {android::util::PROCESS_MEMORY_HIGH_WATER_MARK,
         {{3},
          {2},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
        // temperature
        {android::util::TEMPERATURE, {{}, 1 * NS_PER_SEC, new ResourceThermalManagerPuller()}},
        {android::util::TEMPERATURE, {{}, {}, 1 * NS_PER_SEC, new ResourceThermalManagerPuller()}},
        // binder_calls
        {android::util::BINDER_CALLS,
         {{4, 5, 6, 8, 12},
          {2, 3, 7, 9, 10, 11, 13},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::BINDER_CALLS)}},
        // binder_calls_exceptions
        {android::util::BINDER_CALLS_EXCEPTIONS,
         {{},
          {},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::BINDER_CALLS_EXCEPTIONS)}},
        // looper_stats
        {android::util::LOOPER_STATS,
         {{5, 6, 7, 8, 9},
          {2, 3, 4, 10},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::LOOPER_STATS)}},
        // Disk Stats
        {android::util::DISK_STATS,
         {{}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::DISK_STATS)}},
         {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::DISK_STATS)}},
        // Directory usage
        {android::util::DIRECTORY_USAGE,
         {{}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::DIRECTORY_USAGE)}},
         {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::DIRECTORY_USAGE)}},
        // Size of app's code, data, and cache
        {android::util::APP_SIZE,
         {{}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::APP_SIZE)}},
         {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::APP_SIZE)}},
        // Size of specific categories of files. Eg. Music.
        {android::util::CATEGORY_SIZE,
         {{}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}},
         {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}},
        // Number of fingerprints registered to each user.
        {android::util::NUM_FINGERPRINTS,
         {{}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS)}},
         {{},
          {},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS)}},
        // ProcStats.
        {android::util::PROC_STATS,
         {{}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::PROC_STATS)}},
         {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::PROC_STATS)}},
        // ProcStatsPkgProc.
        {android::util::PROC_STATS_PKG_PROC,
         {{}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::PROC_STATS_PKG_PROC)}},
         {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::PROC_STATS_PKG_PROC)}},
        // Disk I/O stats per uid.
        {android::util::DISK_IO,
         {{2,3,4,5,6,7,8,9,10,11},
          {},
          3 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::DISK_IO)}},
        // PowerProfile constants for power model calculations.
        {android::util::POWER_PROFILE,
         {{}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::POWER_PROFILE)}},
         {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::POWER_PROFILE)}},
        // Process cpu stats. Min cool-down is 5 sec, inline with what AcitivityManagerService uses.
        {android::util::PROCESS_CPU_TIME,
         {{} /* additive fields */,
            {{} /* additive fields */, {} /* non additive fields */,
             5 * NS_PER_SEC /* min cool-down in seconds*/,
             new StatsCompanionServicePuller(android::util::PROCESS_CPU_TIME)}},
        {android::util::CPU_TIME_PER_THREAD_FREQ,
         {{7},
          {2, 3, 4, 5, 6},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}},
        // DeviceCalculatedPowerUse.
        {android::util::DEVICE_CALCULATED_POWER_USE,
         {{},
          1 * NS_PER_SEC,
         {{}, {}, 1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}},
        // DeviceCalculatedPowerBlameUid.
        {android::util::DEVICE_CALCULATED_POWER_BLAME_UID,
         {{},  // BatteryStats already merged isolated with host ids so it's unnecessary here.
         {{}, {}, // BatteryStats already merged isolated with host ids so it's unnecessary here.
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}},
        // DeviceCalculatedPowerBlameOther.
        {android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER,
         {{},
         {{}, {},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
        // BuildInformation.
+3 −0
Original line number Diff line number Diff line
@@ -36,6 +36,9 @@ typedef struct {
    // The field numbers of the fields that need to be summed when merging
    // isolated uid with host uid.
    std::vector<int> additiveFields;
    // The field numbers of the fields that can't be merged when merging
    // data belong to isolated uid and host uid.
    std::vector<int> nonAdditiveFields;
    // How long should the puller wait before doing an actual pull again. Default
    // 1 sec. Set this to 0 if this is handled elsewhere.
    int64_t coolDownNs = 1 * NS_PER_SEC;
+98 −98
Original line number Diff line number Diff line
@@ -25,13 +25,67 @@ namespace android {
namespace os {
namespace statsd {

using std::list;
using std::map;
using std::set;
using std::shared_ptr;
using std::sort;
using std::vector;

namespace {
bool shouldMerge(shared_ptr<LogEvent>& lhs, shared_ptr<LogEvent>& rhs,
                 const vector<int>& nonAdditiveFields) {
    const auto& l_values = lhs->getValues();
    const auto& r_values = rhs->getValues();

    for (size_t i : nonAdditiveFields) {
        // We store everything starting from index 0, so we need to use i-1
        if (!(l_values.size() > i - 1 && r_values.size() > i - 1 &&
              l_values[i - 1].mValue == r_values[i - 1].mValue)) {
            return false;
        }
    }
    return true;
}

// merge rhs to lhs
// when calling this function, all sanity check should be done already.
// e.g., index boundary, nonAdditiveFields matching etc.
bool mergeEvent(shared_ptr<LogEvent>& lhs, shared_ptr<LogEvent>& rhs,
                const vector<int>& additiveFields) {
    vector<FieldValue>* host_values = lhs->getMutableValues();
    const auto& child_values = rhs->getValues();
    for (int i : additiveFields) {
        Value& host = (*host_values)[i - 1].mValue;
        const Value& child = (child_values[i - 1]).mValue;
        if (child.getType() != host.getType()) {
            return false;
        }
        switch (child.getType()) {
            case INT:
                host.setInt(host.int_value + child.int_value);
                break;
            case LONG:
                host.setLong(host.long_value + child.long_value);
                break;
            default:
                ALOGE("Tried to merge 2 fields with unsupported type");
                return false;
        }
    }
    return true;
}

bool tryMerge(vector<shared_ptr<LogEvent>>& data, int child_pos, const vector<int>& host_pos,
              const vector<int>& nonAdditiveFields, const vector<int>& additiveFields) {
    for (const auto& pos : host_pos) {
        if (shouldMerge(data[pos], data[child_pos], nonAdditiveFields) &&
            mergeEvent(data[pos], data[child_pos], additiveFields)) {
            return true;
        }
    }
    return false;
}

}  // namespace

/**
 * Process all data and merge isolated with host if necessary.
 * For example:
@@ -41,7 +95,7 @@ using std::vector;
 *       int byte_send = 3;
 *       int byte_recv = 4;
 *   }
 *   additive fields are {3, 4}
 *   additive fields are {3, 4}, non-additive field is {2}
 * If we pulled the following events (uid1_child is an isolated uid which maps to uid1):
 * [uid1, fg, 100, 200]
 * [uid1_child, fg, 100, 200]
@@ -50,119 +104,65 @@ using std::vector;
 * We want to merge them and results should be:
 * [uid1, fg, 200, 400]
 * [uid1, bg, 100, 200]
 *
 * All atoms should be of the same tagId. All fields should be present.
 */
void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const sp<UidMap>& uidMap,
void mergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const sp<UidMap>& uidMap,
                                int tagId) {
    if (StatsPullerManager::kAllPullAtomInfo.find(tagId) ==
        StatsPullerManager::kAllPullAtomInfo.end()) {
        VLOG("Unknown pull atom id %d", tagId);
        return;
    }
    if ((android::util::AtomsInfo::kAtomsWithAttributionChain.find(tagId) ==
         android::util::AtomsInfo::kAtomsWithAttributionChain.end()) &&
        (android::util::AtomsInfo::kAtomsWithUidField.find(tagId) ==
         android::util::AtomsInfo::kAtomsWithUidField.end())) {
        VLOG("No uid or attribution chain to merge, atom %d", tagId);
    int uidField;
    auto it = android::util::AtomsInfo::kAtomsWithUidField.find(tagId);
    if (it == android::util::AtomsInfo::kAtomsWithUidField.end()) {
        VLOG("No uid to merge for atom %d", tagId);
        return;
    } else {
        uidField = it->second;  // uidField is the field number in proto,
    }
    const vector<int>& additiveFields =
            StatsPullerManager::kAllPullAtomInfo.find(tagId)->second.additiveFields;
    const vector<int>& nonAdditiveFields =
            StatsPullerManager::kAllPullAtomInfo.find(tagId)->second.nonAdditiveFields;

    // 1. Map all isolated uid in-place to host uid
    for (shared_ptr<LogEvent>& event : data) {
        if (event->GetTagId() != tagId) {
            ALOGE("Wrong atom. Expecting %d, got %d", tagId, event->GetTagId());
            return;
        }
        if (android::util::AtomsInfo::kAtomsWithAttributionChain.find(tagId) !=
            android::util::AtomsInfo::kAtomsWithAttributionChain.end()) {
            for (auto& value : *(event->getMutableValues())) {
                if (value.mField.getPosAtDepth(0) > kAttributionField) {
                    break;
                }
                if (isAttributionUidField(value)) {
                    const int hostUid = uidMap->getHostUidOrSelf(value.mValue.int_value);
                    value.mValue.setInt(hostUid);
    // map of host uid to their position in the original vector
    map<int, vector<int>> hostPosition;
    vector<bool> toRemove = vector<bool>(data.size(), false);

    for (size_t i = 0; i < data.size(); i++) {
        vector<FieldValue>* valueList = data[i]->getMutableValues();

        int uid;
        if (uidField > 0 && (int)data[i]->getValues().size() >= uidField &&
            (data[i]->getValues())[uidField - 1].mValue.getType() == INT) {
            uid = (*data[i]->getMutableValues())[uidField - 1].mValue.int_value;
        } else {
            ALOGE("Malformed log, uid not found. %s", data[i]->ToString().c_str());
            continue;
        }

        const int hostUid = uidMap->getHostUidOrSelf(uid);

        if (hostUid != uid) {
            (*valueList)[0].mValue.setInt(hostUid);
        }
        if (hostPosition.find(hostUid) == hostPosition.end()) {
            hostPosition[hostUid].push_back(i);
        } else {
            auto it = android::util::AtomsInfo::kAtomsWithUidField.find(tagId);
            if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
                int uidField = it->second;  // uidField is the field number in proto,
                // starting from 1
                if (uidField > 0 && (int)event->getValues().size() >= uidField &&
                    (event->getValues())[uidField - 1].mValue.getType() == INT) {
                    Value& value = (*event->getMutableValues())[uidField - 1].mValue;
                    const int hostUid = uidMap->getHostUidOrSelf(value.int_value);
                    value.setInt(hostUid);
            if (tryMerge(data, i, hostPosition[hostUid], nonAdditiveFields, additiveFields)) {
                toRemove[i] = true;
            } else {
                    ALOGE("Malformed log, uid not found. %s", event->ToString().c_str());
                    return;
                }
                hostPosition[hostUid].push_back(i);
            }
        }
    }

    // 2. sort the data, bit-wise
    sort(data.begin(), data.end(),
         [](const shared_ptr<LogEvent>& lhs, const shared_ptr<LogEvent>& rhs) {
             if (lhs->size() != rhs->size()) {
                 return lhs->size() < rhs->size();
             }
             const std::vector<FieldValue>& lhsValues = lhs->getValues();
             const std::vector<FieldValue>& rhsValues = rhs->getValues();
             for (int i = 0; i < (int)lhs->size(); i++) {
                 if (lhsValues[i] != rhsValues[i]) {
                     return lhsValues[i] < rhsValues[i];
                 }
             }
             return false;
         });

    vector<shared_ptr<LogEvent>> mergedData;
    const vector<int>& additiveFieldsVec =
            StatsPullerManager::kAllPullAtomInfo.find(tagId)->second.additiveFields;
    const set<int> additiveFields(additiveFieldsVec.begin(), additiveFieldsVec.end());
    bool needMerge = true;

    // 3. do the merge.
    // The loop invariant is this: for every event, check if it differs on
    // non-additive fields, or have different attribution chain length.
    // If so, no need to merge, add itself to the result.
    // Otherwise, merge the value onto the one immediately next to it.
    for (int i = 0; i < (int)data.size() - 1; i++) {
        // Size different, must be different chains.
        if (data[i]->size() != data[i + 1]->size()) {
    for (size_t i = 0; i < toRemove.size(); i++) {
        if (!toRemove[i]) {
            mergedData.push_back(data[i]);
            continue;
        }
        vector<FieldValue>* lhsValues = data[i]->getMutableValues();
        vector<FieldValue>* rhsValues = data[i + 1]->getMutableValues();
        needMerge = true;
        for (int p = 0; p < (int)lhsValues->size(); p++) {
            if ((*lhsValues)[p] != (*rhsValues)[p]) {
                int pos = (*lhsValues)[p].mField.getPosAtDepth(0);
                // Differ on non-additive field, abort.
                if (additiveFields.find(pos) == additiveFields.end()) {
                    needMerge = false;
                    break;
    }
            }
        }
        if (!needMerge) {
            mergedData.push_back(data[i]);
            continue;
        }
        // This should be infrequent operation.
        for (int p = 0; p < (int)lhsValues->size(); p++) {
            int pos = (*lhsValues)[p].mField.getPosAtDepth(0);
            if (additiveFields.find(pos) != additiveFields.end()) {
                (*rhsValues)[p].mValue += (*lhsValues)[p].mValue;
            }
        }
    }
    mergedData.push_back(data.back());

    data.clear();
    data = mergedData;
}
Loading