Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions api/wasm/cpp/proxy_wasm_intrinsics.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,19 @@ extern "C" uint32_t proxy_httpCall(const char* uri_ptr, size_t uri_size, void* h
void* trailer_pairs_ptr, size_t trailer_pairs_size,
uint32_t timeout_milliseconds);

// Metrics

enum class MetricType : uint32_t {
Counter = 0,
Gauge = 1,
Histogram = 2,
};
// Returns a metric_id which can be used to report a metric. On error returns 0.
extern "C" uint32_t proxy_defineMetric(MetricType type, const char* name_ptr, size_t name_size);
extern "C" void proxy_incrementMetric(uint32_t metric_id, int64_t offset);
extern "C" void proxy_recordMetric(uint32_t metric_id, uint64_t value);
extern "C" uint64_t proxy_getMetric(uint32_t metric_id);

//
// High Level C++ API.
//
Expand Down Expand Up @@ -479,3 +492,17 @@ inline uint32_t httpCall(std::string_view uri, const HeaderStringPairs& request_
::free(trailers_ptr);
return result;
}

inline uint32_t defineMetric(MetricType type, std::string_view name) {
return proxy_defineMetric(type, name.data(), name.size());
}

inline void incrementMetric(uint32_t metric_id, int64_t offset) {
proxy_incrementMetric(metric_id, offset);
}

inline void recordMetric(uint32_t metric_id, uint64_t value) {
proxy_recordMetric(metric_id, value);
}

inline uint64_t getMetric(uint32_t metric_id) { return proxy_getMetric(metric_id); }
4 changes: 4 additions & 0 deletions api/wasm/cpp/proxy_wasm_intrinsics.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ mergeInto(LibraryManager.library, {
proxy_removeResponseTrailer: function () {},
proxy_getResponseBodyBufferBytes: function () {},
proxy_httpCall: function () {},
proxy_defineMetric: function () {},
proxy_incrementMetric: function () {},
proxy_recordMetric: function () {},
proxy_getMetric: function () {},
});
20,242 changes: 20,242 additions & 0 deletions examples/wasm/envoy_filter_http_wasm_example.wat

Large diffs are not rendered by default.

Empty file.
4 changes: 4 additions & 0 deletions include/envoy/server/wasm_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class WasmFactoryContext {
* @return Api::Api& a reference to the api object.
*/
virtual Api::Api& api() PURE;
/**
* @return Stats::ScopeSharedPtr shared by all VMs.
*/
virtual Stats::ScopeSharedPtr scope() PURE;
};

/**
Expand Down
2 changes: 1 addition & 1 deletion source/extensions/access_loggers/wasm/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ WasmAccessLogFactory::createAccessLogInstance(const Protobuf::Message& proto_con
// Create a base WASM to verify that the code loads before setting/cloning the for the
// individual threads.
auto base_wasm = Common::Wasm::createWasm(id, config.vm_config(), context.clusterManager(),
context.dispatcher(), context.api());
context.dispatcher(), context.api(), context.scope());
// NB: the Slot set() call doesn't complete inline, so all arguments must outlive this call.
tls_slot->set([base_wasm, configuration](Event::Dispatcher& dispatcher) {
auto result = Common::Wasm::createThreadLocalWasm(*base_wasm, *configuration, dispatcher);
Expand Down
130 changes: 123 additions & 7 deletions source/extensions/common/wasm/wasm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,30 @@ uint32_t httpCallHandler(void* raw_context, uint32_t uri_ptr, uint32_t uri_size,
return context->httpCall(uri, headers, body, trailers, timeout_milliseconds);
}

uint32_t defineMetricHandler(void* raw_context, uint32_t metric_type, uint32_t name_ptr,
uint32_t name_size) {
if (metric_type > static_cast<uint32_t>(Context::MetricType::Max))
return 0;
auto context = WASM_CONTEXT(raw_context);
auto name = context->wasmVm()->getMemory(name_ptr, name_size);
return context->defineMetric(static_cast<Context::MetricType>(metric_type), name);
}

void incrementMetricHandler(void* raw_context, uint32_t metric_id, int64_t offset) {
auto context = WASM_CONTEXT(raw_context);
context->incrementMetric(metric_id, offset);
}

void recordMetricHandler(void* raw_context, uint32_t metric_id, uint64_t value) {
auto context = WASM_CONTEXT(raw_context);
context->recordMetric(metric_id, value);
}

uint64_t getMetricHandler(void* raw_context, uint32_t metric_id) {
auto context = WASM_CONTEXT(raw_context);
return context->getMetric(metric_id);
}

uint32_t getTotalMemoryHandler(void*) { return 0x7FFFFFFF; }
uint32_t _emscripten_get_heap_sizeHandler(void*) { return 0x7FFFFFFF; }
void _llvm_trapHandler(void*) { throw WasmException("emscripten llvm_trap"); }
Expand Down Expand Up @@ -928,10 +952,96 @@ void Context::onHttpCallResponse(uint32_t token, const Pairs& response_headers,
trailers_ptr, trailers_size);
}

uint32_t Context::defineMetric(MetricType type, absl::string_view name) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are iterating over all the possible enum value.
Could you use switch case without default? That's a strong indication that all the values are considered. And modern compiler can guarantee that (if a value is skipped compiler will fail to compile)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly that is only for well behaved code. There is an underlying enum type and nothing prevents a poorly behaved program from mashing that value at which point the non of the cases are executed:

#include

enum class V : int {
No = 0,
Yes = 1,
};

V make_v() {
return static_cast(2);
}

int main() {
V v = make_v();
switch (v) {
case V::No:
std::cout << "No";
break;
case V::Yes:
std::cout << "Yes";
break;
}
std::cout << "Done";
}

./a.out
Done

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was "#include "

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this code. I didn't think of this bad value case. However, isn't this a perfect usage in this case?

  • return id in known value (and early return)
  • replace cout << "Done" by return 0

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to a 'switch'. Code is much larger because of the additional scopes required to handle the local iterator, but it does mean that there will be an error if a new type is added to the enum.

switch (type) {
case MetricType::Counter: {
auto id = wasm_->nextCounterMetricId();
wasm_->counters_.emplace(id, &wasm_->scope_.counter(std::string(
name))); // This is inefficient, but it is the Scope API.
return id;
}
case MetricType::Gauge: {
auto id = wasm_->nextGaugeMetricId();
wasm_->gauges_.emplace(id, &wasm_->scope_.gauge(std::string(
name))); // This is inefficient, but it is the Scope API.
return id;
}
case MetricType::Histogram: {
auto id = wasm_->nextHistogramMetricId();
wasm_->histograms_.emplace(id, &wasm_->scope_.histogram(std::string(
name))); // This is inefficient, but it is the Scope API.
return id;
}
}
return 0;
}

void Context::incrementMetric(uint32_t metric_id, int64_t offset) {
auto type = static_cast<MetricType>(metric_id & Wasm::kMetricTypeMask);
if (type == MetricType::Counter) {
auto it = wasm_->counters_.find(metric_id);
if (it != wasm_->counters_.end()) {
if (offset > 0)
it->second->add(offset);
}
} else if (type == MetricType::Gauge) {
auto it = wasm_->gauges_.find(metric_id);
if (it != wasm_->gauges_.end()) {
if (offset > 0)
it->second->add(offset);
else
it->second->sub(-offset);
}
}
}

void Context::recordMetric(uint32_t metric_id, uint64_t value) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use switch case here as well

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

switch (static_cast<MetricType>(metric_id & Wasm::kMetricTypeMask)) {
case MetricType::Counter: {
auto it = wasm_->counters_.find(metric_id);
if (it != wasm_->counters_.end()) {
it->second->add(value);
}
break;
}
case MetricType::Gauge: {
auto it = wasm_->gauges_.find(metric_id);
if (it != wasm_->gauges_.end()) {
it->second->set(value);
}
break;
}
case MetricType::Histogram: {
auto it = wasm_->histograms_.find(metric_id);
if (it != wasm_->histograms_.end()) {
it->second->recordValue(value);
}
break;
}
}
}

uint64_t Context::getMetric(uint32_t metric_id) {
auto type = static_cast<MetricType>(metric_id & Wasm::kMetricTypeMask);
if (type == MetricType::Counter) {
auto it = wasm_->counters_.find(metric_id);
if (it != wasm_->counters_.end()) {
return it->second->value();
}
} else if (type == MetricType::Gauge) {
auto it = wasm_->gauges_.find(metric_id);
if (it != wasm_->gauges_.end()) {
return it->second->value();
}
}
return 0;
}

Wasm::Wasm(absl::string_view vm, absl::string_view id, absl::string_view initial_configuration,
Upstream::ClusterManager& cluster_manager, Event::Dispatcher& dispatcher)
: cluster_manager_(cluster_manager), dispatcher_(dispatcher),
initial_configuration_(initial_configuration) {
Upstream::ClusterManager& cluster_manager, Event::Dispatcher& dispatcher,
Stats::Scope& scope, Stats::ScopeSharedPtr owned_scope)
: cluster_manager_(cluster_manager), dispatcher_(dispatcher), scope_(scope),
owned_scope_(owned_scope), initial_configuration_(initial_configuration) {
wasm_vm_ = Common::Wasm::createWasmVm(vm);
id_ = std::string(id);
if (wasm_vm_) {
Expand Down Expand Up @@ -992,6 +1102,11 @@ Wasm::Wasm(absl::string_view vm, absl::string_view id, absl::string_view initial
_REGISTER_PROXY(httpCall);

_REGISTER_PROXY(setTickPeriodMilliseconds);

_REGISTER_PROXY(defineMetric);
_REGISTER_PROXY(incrementMetric);
_REGISTER_PROXY(recordMetric);
_REGISTER_PROXY(getMetric);
#undef _REGISTER_PROXY
}
}
Expand Down Expand Up @@ -1020,7 +1135,7 @@ void Wasm::getFunctions() {

Wasm::Wasm(const Wasm& wasm)
: std::enable_shared_from_this<Wasm>(), cluster_manager_(wasm.cluster_manager_),
dispatcher_(wasm.dispatcher_) {
dispatcher_(wasm.dispatcher_), scope_(wasm.scope_), owned_scope_(wasm.owned_scope_) {
wasm_vm_ = wasm.wasmVm()->clone();
general_context_ = createContext();
getFunctions();
Expand Down Expand Up @@ -1224,9 +1339,10 @@ std::unique_ptr<WasmVm> createWasmVm(absl::string_view wasm_vm) {
std::shared_ptr<Wasm> createWasm(absl::string_view id,
const envoy::config::wasm::v2::VmConfig& vm_config,
Upstream::ClusterManager& cluster_manager,
Event::Dispatcher& dispatcher, Api::Api& api) {
Event::Dispatcher& dispatcher, Api::Api& api, Stats::Scope& scope,
Stats::ScopeSharedPtr scope_ptr) {
auto wasm = std::make_shared<Wasm>(vm_config.vm(), id, vm_config.initial_configuration(),
cluster_manager, dispatcher);
cluster_manager, dispatcher, scope, scope_ptr);
const auto& code = Config::DataSource::read(vm_config.code(), true, api);
const auto& path = Config::DataSource::getPath(vm_config.code())
.value_or(code.empty() ? EMPTY_STRING : INLINE_STRING);
Expand All @@ -1248,7 +1364,7 @@ std::shared_ptr<Wasm> createThreadLocalWasm(Wasm& base_wasm, absl::string_view c
} else {
wasm = std::make_shared<Wasm>(base_wasm.wasmVm()->vm(), base_wasm.id(),
base_wasm.initial_configuration(), base_wasm.clusterManager(),
dispatcher);
dispatcher, base_wasm.scope());
if (!wasm->initialize(base_wasm.code(), base_wasm.id(), base_wasm.allow_precompiled())) {
throw WasmException("Failed to initialize WASM code");
}
Expand Down
50 changes: 48 additions & 2 deletions source/extensions/common/wasm/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include "envoy/config/wasm/v2/wasm.pb.validate.h"
#include "envoy/http/filter.h"
#include "envoy/server/wasm.h"
#include "envoy/stats/scope.h"
#include "envoy/stats/stats.h"
#include "envoy/thread_local/thread_local.h"
#include "envoy/upstream/cluster_manager.h"

Expand Down Expand Up @@ -196,6 +198,18 @@ class Context : public Http::StreamFilter,
virtual void httpRespond(const Pairs& response_headers, absl::string_view body,
const Pairs& response_trailers);

// Stats/Metrics
enum class MetricType : uint32_t {
Counter = 0,
Gauge = 1,
Histogram = 2,
Max = 2,
};
virtual uint32_t defineMetric(MetricType type, absl::string_view name);
virtual void incrementMetric(uint32_t metric_id, int64_t offset);
virtual void recordMetric(uint32_t metric_id, uint64_t value);
virtual uint64_t getMetric(uint32_t metric_id);

// Connection
virtual bool isSsl();

Expand Down Expand Up @@ -253,7 +267,8 @@ class Wasm : public Envoy::Server::Wasm,
public std::enable_shared_from_this<Wasm> {
public:
Wasm(absl::string_view vm, absl::string_view id, absl::string_view initial_configuration,
Upstream::ClusterManager& cluster_manager, Event::Dispatcher& dispatcher);
Upstream::ClusterManager& cluster_manager, Event::Dispatcher& dispatcher,
Stats::Scope& scope, Stats::ScopeSharedPtr owned_scope = nullptr);
Wasm(const Wasm& other);
~Wasm() {}

Expand All @@ -268,6 +283,7 @@ class Wasm : public Envoy::Server::Wasm,
WasmVm* wasmVm() const { return wasm_vm_.get(); }
Context* generalContext() const { return general_context_.get(); }
Upstream::ClusterManager& clusterManager() const { return cluster_manager_; }
Stats::Scope& scope() const { return scope_; }

std::shared_ptr<Context> createContext() { return std::make_shared<Context>(this); }

Expand Down Expand Up @@ -296,11 +312,30 @@ class Wasm : public Envoy::Server::Wasm,

private:
friend class Context;
static constexpr uint32_t kMetricTypeMask = 0x3;
static constexpr uint32_t kMetricTypeCounter = 0x0;
static constexpr uint32_t kMetricTypeGauge = 0x1;
static constexpr uint32_t kMetricTypeHistogram = 0x2;
static constexpr uint32_t kMetricIdIncrement = 0x4;

bool isCounterMetricId(uint32_t metric_id) {
return (metric_id & kMetricTypeMask) == kMetricTypeCounter;
}
bool isGaugeMetricId(uint32_t metric_id) {
return (metric_id & kMetricTypeMask) == kMetricTypeGauge;
}
bool isHistogramMetricId(uint32_t metric_id) {
return (metric_id & kMetricTypeMask) == kMetricTypeHistogram;
}
uint32_t nextCounterMetricId() { return next_counter_metric_id_ += kMetricIdIncrement; }
uint32_t nextGaugeMetricId() { return next_gauge_metric_id_ += kMetricIdIncrement; }
uint32_t nextHistogramMetricId() { return next_histogram_metric_id_ += kMetricIdIncrement; }

void getFunctions();

Upstream::ClusterManager& cluster_manager_;
Event::Dispatcher& dispatcher_;
Stats::Scope& scope_;
std::string id_;
std::string context_id_filter_state_data_name_;
uint32_t next_context_id_ = 0;
Expand All @@ -309,6 +344,8 @@ class Wasm : public Envoy::Server::Wasm,
std::function<void(Common::Wasm::Context*)> tick_;
std::chrono::milliseconds tick_period_;
Event::TimerPtr timer_;
Stats::ScopeSharedPtr
owned_scope_; // When scope_ is not owned by a higher level (e.g. for WASM services).

// Calls into the VM.
WasmCall0Void onStart_;
Expand Down Expand Up @@ -338,6 +375,14 @@ class Wasm : public Envoy::Server::Wasm,
std::string code_;
std::string initial_configuration_;
bool allow_precompiled_ = false;

// Stats/Metrics
uint32_t next_counter_metric_id_ = kMetricTypeCounter;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constexpr ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are mutable. They start with the say kMetricTypeCounter then are incremented by kMetricIdIncrement to get new counter specific indexes.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get it. Thanks!

uint32_t next_gauge_metric_id_ = kMetricTypeGauge;
uint32_t next_histogram_metric_id_ = kMetricTypeHistogram;
absl::flat_hash_map<uint32_t, Stats::Counter*> counters_;
absl::flat_hash_map<uint32_t, Stats::Gauge*> gauges_;
absl::flat_hash_map<uint32_t, Stats::Histogram*> histograms_;
};

inline WasmVm* Context::wasmVm() const { return wasm_->wasmVm(); }
Expand Down Expand Up @@ -456,7 +501,8 @@ std::unique_ptr<WasmVm> createWasmVm(absl::string_view vm);
std::shared_ptr<Wasm> createWasm(absl::string_view id,
const envoy::config::wasm::v2::VmConfig& vm_config,
Upstream::ClusterManager& cluster_manager,
Event::Dispatcher& dispatcher, Api::Api& api);
Event::Dispatcher& dispatcher, Api::Api& api, Stats::Scope& scope,
Stats::ScopeSharedPtr owned_scope = nullptr);

// Create a ThreadLocal VM from an existing VM (e.g. from createWasm() above).
std::shared_ptr<Wasm> createThreadLocalWasm(Wasm& base_wasm, absl::string_view configuration,
Expand Down
9 changes: 9 additions & 0 deletions source/extensions/common/wasm/wavm/wavm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,15 @@ template void registerCallbackWavm<U32, void*, U32, U32, U32, U32, U32, U32, U32
WasmVm* vm, absl::string_view functionName,
U32 (*f)(void*, U32, U32, U32, U32, U32, U32, U32, U32, U32, U32));

template void registerCallbackWavm<U64, void*, U32>(WasmVm* vm, absl::string_view functionName,
U64 (*f)(void*, U32));
template void registerCallbackWavm<void, void*, U32, I64>(WasmVm* vm,
absl::string_view functionName,
void (*f)(void*, U32, I64));
template void registerCallbackWavm<void, void*, U32, U64>(WasmVm* vm,
absl::string_view functionName,
void (*f)(void*, U32, U64));

template <typename R, typename... Args>
IR::FunctionType
inferStdFunctionType(std::function<R(Envoy::Extensions::Common::Wasm::Context*, Args...)>*) {
Expand Down
2 changes: 1 addition & 1 deletion source/extensions/filters/http/wasm/wasm_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ FilterConfig::FilterConfig(const envoy::config::filter::http::wasm::v2::Wasm& co
// Create a base WASM to verify that the code loads before setting/cloning the for the
// individual threads.
auto base_wasm = Common::Wasm::createWasm(id, config.vm_config(), context.clusterManager(),
context.dispatcher(), context.api());
context.dispatcher(), context.api(), context.scope());
// NB: the Slot set() call doesn't complete inline, so all arguments must outlive this call.
tls_slot_->set([base_wasm, configuration](Event::Dispatcher& dispatcher) {
auto result =
Expand Down
6 changes: 3 additions & 3 deletions source/extensions/wasm/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ Server::WasmSharedPtr WasmFactory::createWasm(const envoy::config::wasm::v2::Was
Server::Configuration::WasmFactoryContext& context) {
// Create a base WASM to verify that the code loads before setting/cloning the for the individual
// threads.
auto base_wasm =
Common::Wasm::createWasm(config.id(), config.vm_config(), context.clusterManager(),
context.dispatcher(), context.api());
auto base_wasm = Common::Wasm::createWasm(config.id(), config.vm_config(),
context.clusterManager(), context.dispatcher(),
context.api(), *context.scope(), context.scope());
if (config.singleton()) {
// Return the WASM VM which will be stored as a singleton by the Server.
return base_wasm;
Expand Down
Loading