Skip to content

Commit

Permalink
[API] Added algo.Percentile for percentile calculation of things like…
Browse files Browse the repository at this point in the history
… latencies.

We are only using a simple nearest-rank method at this point. Interpolation method
could be implemented in the future.
  • Loading branch information
pajama-coder committed Jun 9, 2021
1 parent 4825391 commit d54c0aa
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 3 deletions.
88 changes: 88 additions & 0 deletions src/api/algo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,55 @@ void ResourcePool::free_tenant(const pjs::Value &tenant) {
m_tenants.erase(i);
}

//
// Percentile
//

Percentile::Percentile(pjs::Array *scores)
: m_scores(scores->length())
, m_buckets(scores->length())
{
double last = std::numeric_limits<double>::min();
scores->iterate_all(
[&](pjs::Value &v, int i) {
auto score = v.to_number();
if (score <= last) throw std::runtime_error("scores are not in ascending order");
m_scores[i] = score;
last = score;
}
);

reset();
}

void Percentile::reset() {
for (auto &n : m_buckets) n = 0;
m_score_count = 0;
}

void Percentile::score(double score) {
for (size_t i = 0, n = m_scores.size(); i < n; i++) {
if (score <= m_scores[i]) {
m_buckets[i]++;
m_score_count++;
break;
}
}
}

auto Percentile::calculate(int percentage) -> double {
if (percentage <= 0) return 0;
size_t total = m_score_count * percentage / 100;
size_t count = 0;
for (size_t i = 0, n = m_buckets.size(); i < n; i++) {
count += m_buckets[i];
if (count >= total) {
return m_scores[i];
}
}
return std::numeric_limits<double>::infinity();
}

} // namespace algo
} // namespace pipy

Expand Down Expand Up @@ -723,6 +772,44 @@ template<> void ClassDef<Constructor<ResourcePool>>::init() {
ctor();
}

//
// Percentile
//

template<> void ClassDef<Percentile>::init() {
ctor([](Context &ctx) -> Object* {
Array *scores;
if (!ctx.arguments(1, &scores)) return nullptr;
try {
return Percentile::make(scores);
} catch (std::runtime_error &err) {
ctx.error(err);
return nullptr;
}
});

method("reset", [](Context &ctx, Object *obj, Value &ret) {
obj->as<Percentile>()->reset();
});

method("score", [](Context &ctx, Object *obj, Value &ret) {
double score;
if (!ctx.arguments(1, &score)) return;
obj->as<Percentile>()->score(score);
});

method("calculate", [](Context &ctx, Object *obj, Value &ret) {
int percentage;
if (!ctx.arguments(1, &percentage)) return;
ret.set(obj->as<Percentile>()->calculate(percentage));
});
}

template<> void ClassDef<Constructor<Percentile>>::init() {
super<Function>();
ctor();
}

//
// Algo
//
Expand All @@ -735,6 +822,7 @@ template<> void ClassDef<Algo>::init() {
variable("RoundRobinLoadBalancer", class_of<Constructor<RoundRobinLoadBalancer>>());
variable("LeastWorkLoadBalancer", class_of<Constructor<LeastWorkLoadBalancer>>());
variable("ResourcePool", class_of<Constructor<ResourcePool>>());
variable("Percentile", class_of<Constructor<Percentile>>());

method("hash", [](Context &ctx, Object *obj, Value &ret) {
Value value;
Expand Down
20 changes: 20 additions & 0 deletions src/api/algo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,26 @@ class ResourcePool : public pjs::ObjectTemplate<ResourcePool> {
friend class pjs::ObjectTemplate<ResourcePool>;
};

//
// Percentile
//

class Percentile : public pjs::ObjectTemplate<Percentile> {
public:
void reset();
void score(double score);
auto calculate(int percentage) -> double;

private:
Percentile(pjs::Array *scores);

std::vector<double> m_scores;
std::vector<size_t> m_buckets;
size_t m_score_count;

friend class pjs::ObjectTemplate<Percentile>;
};

//
// Algo
//
Expand Down
1 change: 0 additions & 1 deletion src/api/hessian.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ template<> void ClassDef<Hessian>::init() {
method("decode", [](Context &ctx, Object *obj, Value &ret) {
pipy::Data *data;
if (!ctx.arguments(1, &data)) return;
if (!data) { ret = Value::null; return; }
try {
Hessian::decode(*data, ret);
} catch (std::runtime_error &err) {
Expand Down
4 changes: 2 additions & 2 deletions src/pjs/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1279,15 +1279,15 @@ class Context {

template<class T>
bool get_arg(bool set_error, int i, T **o) {
if (!arg(i).is_null() && !arg(i).is_instance_of<T>()) {
if (arg(i).is_null() || !arg(i).is_instance_of<T>()) {
if (set_error) {
std::string type("an instance of ");
type += class_of<T>()->name();
error_argument_type(i, type.c_str());
}
return false;
}
*o = arg(i).is_null() ? nullptr : arg(i).as<T>();
*o = arg(i).as<T>();
return true;
}
};
Expand Down

0 comments on commit d54c0aa

Please sign in to comment.