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

Commit a27a2647 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "libtimeinstate: add functions to read only recently-updated stats" am:...

Merge "libtimeinstate: add functions to read only recently-updated stats" am: 2bf8f709 am: 5b0a02fa am: d6cb760a

Change-Id: I945e7840b66d82f41bf643110ac446f21231a4f3
parents d7b72086 d6cb760a
Loading
Loading
Loading
Loading
+52 −3
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ static std::vector<std::vector<uint32_t>> gPolicyCpus;
static std::set<uint32_t> gAllFreqs;
static unique_fd gTisMapFd;
static unique_fd gConcurrentMapFd;
static unique_fd gUidLastUpdateMapFd;

static std::optional<std::vector<uint32_t>> readNumbersFromFile(const std::string &path) {
    std::string data;
@@ -144,6 +145,10 @@ static bool initGlobals() {
            unique_fd{bpf_obj_get(BPF_FS_PATH "map_time_in_state_uid_concurrent_times_map")};
    if (gConcurrentMapFd < 0) return false;

    gUidLastUpdateMapFd =
            unique_fd{bpf_obj_get(BPF_FS_PATH "map_time_in_state_uid_last_update_map")};
    if (gUidLastUpdateMapFd < 0) return false;

    gInitialized = true;
    return true;
}
@@ -263,6 +268,18 @@ std::optional<std::vector<std::vector<uint64_t>>> getUidCpuFreqTimes(uint32_t ui
    return out;
}

static std::optional<bool> uidUpdatedSince(uint32_t uid, uint64_t lastUpdate,
                                           uint64_t *newLastUpdate) {
    uint64_t uidLastUpdate;
    if (findMapEntry(gUidLastUpdateMapFd, &uid, &uidLastUpdate)) return {};
    // Updates that occurred during the previous read may have been missed. To mitigate
    // this, don't ignore entries updated up to 1s before *lastUpdate
    constexpr uint64_t NSEC_PER_SEC = 1000000000;
    if (uidLastUpdate + NSEC_PER_SEC < lastUpdate) return false;
    if (uidLastUpdate > *newLastUpdate) *newLastUpdate = uidLastUpdate;
    return true;
}

// Retrieve the times in ns that each uid spent running at each CPU freq.
// Return contains no value on error, otherwise it contains a map from uids to vectors of vectors
// using the format:
@@ -271,6 +288,14 @@ std::optional<std::vector<std::vector<uint64_t>>> getUidCpuFreqTimes(uint32_t ui
// where ti_j_k is the ns uid i spent running on the jth cluster at the cluster's kth lowest freq.
std::optional<std::unordered_map<uint32_t, std::vector<std::vector<uint64_t>>>>
getUidsCpuFreqTimes() {
    return getUidsUpdatedCpuFreqTimes(nullptr);
}

// Retrieve the times in ns that each uid spent running at each CPU freq, excluding UIDs that have
// not run since before lastUpdate.
// Return format is the same as getUidsCpuFreqTimes()
std::optional<std::unordered_map<uint32_t, std::vector<std::vector<uint64_t>>>>
getUidsUpdatedCpuFreqTimes(uint64_t *lastUpdate) {
    if (!gInitialized && !initGlobals()) return {};
    time_key_t key, prevKey;
    std::unordered_map<uint32_t, std::vector<std::vector<uint64_t>>> map;
@@ -282,8 +307,14 @@ getUidsCpuFreqTimes() {
    std::vector<std::vector<uint64_t>> mapFormat;
    for (const auto &freqList : gPolicyFreqs) mapFormat.emplace_back(freqList.size(), 0);

    uint64_t newLastUpdate = lastUpdate ? *lastUpdate : 0;
    std::vector<tis_val_t> vals(gNCpus);
    do {
        if (lastUpdate) {
            auto uidUpdated = uidUpdatedSince(key.uid, *lastUpdate, &newLastUpdate);
            if (!uidUpdated.has_value()) return {};
            if (!*uidUpdated) continue;
        }
        if (findMapEntry(gTisMapFd, &key, vals.data())) return {};
        if (map.find(key.uid) == map.end()) map.emplace(key.uid, mapFormat);

@@ -299,8 +330,9 @@ getUidsCpuFreqTimes() {
            }
        }
        prevKey = key;
    } while (!getNextMapKey(gTisMapFd, &prevKey, &key));
    } while (prevKey = key, !getNextMapKey(gTisMapFd, &prevKey, &key));
    if (errno != ENOENT) return {};
    if (lastUpdate && newLastUpdate > *lastUpdate) *lastUpdate = newLastUpdate;
    return map;
}

@@ -365,6 +397,15 @@ std::optional<concurrent_time_t> getUidConcurrentTimes(uint32_t uid, bool retry)
// where ai is the ns spent running concurrently with tasks on i other cpus and pi_j is the ns spent
// running on the ith cluster, concurrently with tasks on j other cpus in the same cluster.
std::optional<std::unordered_map<uint32_t, concurrent_time_t>> getUidsConcurrentTimes() {
    return getUidsUpdatedConcurrentTimes(nullptr);
}

// Retrieve the times in ns that each uid spent running concurrently with each possible number of
// other tasks on each cluster (policy times) and overall (active times), excluding UIDs that have
// not run since before lastUpdate.
// Return format is the same as getUidsConcurrentTimes()
std::optional<std::unordered_map<uint32_t, concurrent_time_t>> getUidsUpdatedConcurrentTimes(
        uint64_t *lastUpdate) {
    if (!gInitialized && !initGlobals()) return {};
    time_key_t key, prevKey;
    std::unordered_map<uint32_t, concurrent_time_t> ret;
@@ -379,7 +420,13 @@ std::optional<std::unordered_map<uint32_t, concurrent_time_t>> getUidsConcurrent
    std::vector<concurrent_val_t> vals(gNCpus);
    std::vector<uint64_t>::iterator activeBegin, activeEnd, policyBegin, policyEnd;

    uint64_t newLastUpdate = lastUpdate ? *lastUpdate : 0;
    do {
        if (lastUpdate) {
            auto uidUpdated = uidUpdatedSince(key.uid, *lastUpdate, &newLastUpdate);
            if (!uidUpdated.has_value()) return {};
            if (!*uidUpdated) continue;
        }
        if (findMapEntry(gConcurrentMapFd, &key, vals.data())) return {};
        if (ret.find(key.uid) == ret.end()) ret.emplace(key.uid, retFormat);

@@ -405,8 +452,7 @@ std::optional<std::unordered_map<uint32_t, concurrent_time_t>> getUidsConcurrent
                               std::plus<uint64_t>());
            }
        }
        prevKey = key;
    } while (!getNextMapKey(gConcurrentMapFd, &prevKey, &key));
    } while (prevKey = key, !getNextMapKey(gConcurrentMapFd, &prevKey, &key));
    if (errno != ENOENT) return {};
    for (const auto &[key, value] : ret) {
        if (!verifyConcurrentTimes(value)) {
@@ -414,6 +460,7 @@ std::optional<std::unordered_map<uint32_t, concurrent_time_t>> getUidsConcurrent
            if (val.has_value()) ret[key] = val.value();
        }
    }
    if (lastUpdate && newLastUpdate > *lastUpdate) *lastUpdate = newLastUpdate;
    return ret;
}

@@ -446,6 +493,8 @@ bool clearUidTimes(uint32_t uid) {
            return false;
        if (deleteMapEntry(gConcurrentMapFd, &key) && errno != ENOENT) return false;
    }

    if (deleteMapEntry(gUidLastUpdateMapFd, &uid) && errno != ENOENT) return false;
    return true;
}

+4 −0
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ bool startTrackingUidTimes();
std::optional<std::vector<std::vector<uint64_t>>> getUidCpuFreqTimes(uint32_t uid);
std::optional<std::unordered_map<uint32_t, std::vector<std::vector<uint64_t>>>>
    getUidsCpuFreqTimes();
std::optional<std::unordered_map<uint32_t, std::vector<std::vector<uint64_t>>>>
    getUidsUpdatedCpuFreqTimes(uint64_t *lastUpdate);
std::optional<std::vector<std::vector<uint32_t>>> getCpuFreqs();

struct concurrent_time_t {
@@ -35,6 +37,8 @@ struct concurrent_time_t {

std::optional<concurrent_time_t> getUidConcurrentTimes(uint32_t uid, bool retry = true);
std::optional<std::unordered_map<uint32_t, concurrent_time_t>> getUidsConcurrentTimes();
std::optional<std::unordered_map<uint32_t, concurrent_time_t>>
    getUidsUpdatedConcurrentTimes(uint64_t *lastUpdate);
bool clearUidTimes(unsigned int uid);

} // namespace bpf
+180 −76
Original line number Diff line number Diff line
@@ -115,12 +115,14 @@ TEST(TimeInStateTest, SingleUidTimesConsistent) {
}

TEST(TimeInStateTest, AllUidTimeInState) {
    vector<size_t> sizes;
    auto map = getUidsCpuFreqTimes();
    uint64_t zero = 0;
    auto maps = {getUidsCpuFreqTimes(), getUidsUpdatedCpuFreqTimes(&zero)};
    for (const auto &map : maps) {
        ASSERT_TRUE(map.has_value());

        ASSERT_FALSE(map->empty());

        vector<size_t> sizes;
        auto firstEntry = map->begin()->second;
        for (const auto &subEntry : firstEntry) sizes.emplace_back(subEntry.size());

@@ -129,9 +131,63 @@ TEST(TimeInStateTest, AllUidTimeInState) {
            for (size_t i = 0; i < vec.second.size(); ++i) ASSERT_EQ(vec.second[i].size(), sizes[i]);
        }
    }
}

void TestCheckUpdate(const std::vector<std::vector<uint64_t>> &before,
                     const std::vector<std::vector<uint64_t>> &after) {
    ASSERT_EQ(before.size(), after.size());
    uint64_t sumBefore = 0, sumAfter = 0;
    for (size_t i = 0; i < before.size(); ++i) {
        ASSERT_EQ(before[i].size(), after[i].size());
        for (size_t j = 0; j < before[i].size(); ++j) {
            // Times should never decrease
            ASSERT_LE(before[i][j], after[i][j]);
        }
        sumBefore += std::accumulate(before[i].begin(), before[i].end(), (uint64_t)0);
        sumAfter += std::accumulate(after[i].begin(), after[i].end(), (uint64_t)0);
    }
    ASSERT_LE(sumBefore, sumAfter);
    ASSERT_LE(sumAfter - sumBefore, NSEC_PER_SEC);
}

TEST(TimeInStateTest, AllUidUpdatedTimeInState) {
    uint64_t lastUpdate = 0;
    auto map1 = getUidsUpdatedCpuFreqTimes(&lastUpdate);
    ASSERT_TRUE(map1.has_value());
    ASSERT_FALSE(map1->empty());
    ASSERT_NE(lastUpdate, (uint64_t)0);
    uint64_t oldLastUpdate = lastUpdate;

    // Sleep briefly to trigger a context switch, ensuring we see at least one update.
    struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = 1000000;
    nanosleep (&ts, NULL);

    auto map2 = getUidsUpdatedCpuFreqTimes(&lastUpdate);
    ASSERT_TRUE(map2.has_value());
    ASSERT_FALSE(map2->empty());
    ASSERT_NE(lastUpdate, oldLastUpdate);

    bool someUidsExcluded = false;
    for (const auto &[uid, v] : *map1) {
        if (map2->find(uid) == map2->end()) {
            someUidsExcluded = true;
            break;
        }
    }
    ASSERT_TRUE(someUidsExcluded);

    for (const auto &[uid, newTimes] : *map2) {
        ASSERT_NE(map1->find(uid), map1->end());
        ASSERT_NO_FATAL_FAILURE(TestCheckUpdate((*map1)[uid], newTimes));
    }
}

TEST(TimeInStateTest, SingleAndAllUidTimeInStateConsistent) {
    auto map = getUidsCpuFreqTimes();
    uint64_t zero = 0;
    auto maps = {getUidsCpuFreqTimes(), getUidsUpdatedCpuFreqTimes(&zero)};
    for (const auto &map : maps) {
        ASSERT_TRUE(map.has_value());
        ASSERT_FALSE(map->empty());

@@ -150,9 +206,12 @@ TEST(TimeInStateTest, SingleAndAllUidTimeInStateConsistent) {
            }
        }
    }
}

TEST(TimeInStateTest, AllUidConcurrentTimes) {
    auto map = getUidsConcurrentTimes();
    uint64_t zero = 0;
    auto maps = {getUidsConcurrentTimes(), getUidsUpdatedConcurrentTimes(&zero)};
    for (const auto &map : maps) {
        ASSERT_TRUE(map.has_value());
        ASSERT_FALSE(map->empty());

@@ -165,9 +224,47 @@ TEST(TimeInStateTest, AllUidConcurrentTimes) {
            }
        }
    }
}

TEST(TimeInStateTest, AllUidUpdatedConcurrentTimes) {
    uint64_t lastUpdate = 0;
    auto map1 = getUidsUpdatedConcurrentTimes(&lastUpdate);
    ASSERT_TRUE(map1.has_value());
    ASSERT_FALSE(map1->empty());
    ASSERT_NE(lastUpdate, (uint64_t)0);

    // Sleep briefly to trigger a context switch, ensuring we see at least one update.
    struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = 1000000;
    nanosleep (&ts, NULL);

    uint64_t oldLastUpdate = lastUpdate;
    auto map2 = getUidsUpdatedConcurrentTimes(&lastUpdate);
    ASSERT_TRUE(map2.has_value());
    ASSERT_FALSE(map2->empty());
    ASSERT_NE(lastUpdate, oldLastUpdate);

    bool someUidsExcluded = false;
    for (const auto &[uid, v] : *map1) {
        if (map2->find(uid) == map2->end()) {
            someUidsExcluded = true;
            break;
        }
    }
    ASSERT_TRUE(someUidsExcluded);

    for (const auto &[uid, newTimes] : *map2) {
        ASSERT_NE(map1->find(uid), map1->end());
        ASSERT_NO_FATAL_FAILURE(TestCheckUpdate({(*map1)[uid].active},{newTimes.active}));
        ASSERT_NO_FATAL_FAILURE(TestCheckUpdate((*map1)[uid].policy, newTimes.policy));
    }
}

TEST(TimeInStateTest, SingleAndAllUidConcurrentTimesConsistent) {
    auto map = getUidsConcurrentTimes();
    uint64_t zero = 0;
    auto maps = {getUidsConcurrentTimes(), getUidsUpdatedConcurrentTimes(&zero)};
    for (const auto &map : maps) {
        ASSERT_TRUE(map.has_value());
        for (const auto &kv : *map) {
            uint32_t uid = kv.first;
@@ -184,6 +281,7 @@ TEST(TimeInStateTest, SingleAndAllUidConcurrentTimesConsistent) {
            }
        }
    }
}

void TestCheckDelta(uint64_t before, uint64_t after) {
    // Times should never decrease
@@ -242,7 +340,9 @@ TEST(TimeInStateTest, AllUidConcurrentTimesMonotonic) {
}

TEST(TimeInStateTest, AllUidTimeInStateSanityCheck) {
    auto map = getUidsCpuFreqTimes();
    uint64_t zero = 0;
    auto maps = {getUidsCpuFreqTimes(), getUidsUpdatedCpuFreqTimes(&zero)};
    for (const auto &map : maps) {
        ASSERT_TRUE(map.has_value());

        bool foundLargeValue = false;
@@ -258,9 +358,12 @@ TEST(TimeInStateTest, AllUidTimeInStateSanityCheck) {
        // uint64_t as expected, we should have some times higher than that.
        ASSERT_TRUE(foundLargeValue);
    }
}

TEST(TimeInStateTest, AllUidConcurrentTimesSanityCheck) {
    auto concurrentMap = getUidsConcurrentTimes();
    uint64_t zero = 0;
    auto maps = {getUidsConcurrentTimes(), getUidsUpdatedConcurrentTimes(&zero)};
    for (const auto &concurrentMap : maps) {
        ASSERT_TRUE(concurrentMap);

        bool activeFoundLargeValue = false;
@@ -282,6 +385,7 @@ TEST(TimeInStateTest, AllUidConcurrentTimesSanityCheck) {
        ASSERT_TRUE(activeFoundLargeValue);
        ASSERT_TRUE(policyFoundLargeValue);
    }
}

TEST(TimeInStateTest, AllUidTimesConsistent) {
    auto tisMap = getUidsCpuFreqTimes();