prometheus / client_rust Goto Github PK
View Code? Open in Web Editor NEWPrometheus / OpenMetrics client library in Rust
License: Apache License 2.0
Prometheus / OpenMetrics client library in Rust
License: Apache License 2.0
I want to have a GaugeWithExemplar
that adds an exemplar whenever gauge derivates from zero. The actual use-case is making a flamegraph and putting it into the exemplar label, so that it can be easily found.
It seems like it would mostly be a copy of CounterWithExemplar
, so I'm happy to make a PR if this makes sense.
Support deriving Encode
for New Type Idiom.
#[derive(Encode)]
struct SomeNewType(u64)
I am looking for an equivalent to remove_label_values
from prometheus
crate. Is there a way to remove an entry from a metrics Family?
Hi! Thank your for the project :)
I found myself missing an implementation of the Summary metric, so decided to file an issue in case anyone (maybe myself) decides to contribute an implementation.
Open Metrics spec defines a metric type that computes quantiles locally on the client: Summary.
It's quite useful if you want to learn/discover how a system behaves, especially if you don't have much data a-priori. In that sense, Summary is dual to Histogram - both can be used to understand data distribution (e.g. latency data), but with different use-cases and tradeoffs.
Good overview on the differences between Summary and Histogram metrics is given in Prometheus doc https://prometheus.io/docs/practices/histograms/
When run in debug mode, I think it is worth exploring running different validations on the registered metrics.
Examples:
_total
as that is added automatically already.Registry::register
could ensure that no duplicate metric names are being registered.Registry::register
could ensure that the user did not provide an additional .
in the HELP
text. See #56I think a goal worth striving for is keeping the dependency tree small for the sake of compile times.
Sample code to reproduce an issue:
let family = Family::<Vec<(String, String)>, Counter>::default();
// ...
family
.get_or_create(&vec![
("method".to_string(), "GET".to_string()),
("status".to_string(), "200".to_string()),
])
.inc();
family
.get_or_create(&vec![
("method".to_string(), "POST".to_string()),
("status".to_string(), "200".to_string()),
])
.inc();
let metric_set = prometheus_client::encoding::proto::encode(®istry).unwrap();
The encoded counter metrics are:
labels:
* method: GET
* status: 200
value:
* total: 1
labels:
* method: GET
* status: 200
* method: POST
* status: 200
value:
* total: 1
The second metrics above has duplicated labels.
We expect the encoded counter metrics looks like:
labels:
* method: GET
* status: 200
value:
* total: 1
labels:
* method: POST
* status: 200
value:
* total: 1
I have added a test case to reproduce this issue on #123. I was looking into this but couldn't find how to fix. 😓
I saw this issue on protobuf
encoding, it might also got on the text
encoding.
I would like to send (partial) metrics stored the Registry
in original JSON format differ from OpenMetrics, into an endpoint different from Prometheus server.
Here is a pseudo code, shows what I want to do:
let mut registry = <Registry>::default();
// (omitted)
// Using the OpenMetrics format.
let mut buffer = vec![];
encode(&mut buffer, ®istry).unwrap();
// On the other hand, send (partial) metrics in JSON, into an endpoint different from Prometheus server.
let mut data = vec![];
for (descriptor, metric) in registry.iter() {
if descriptor.name() == "__test__" {
data.push(
translate_to_json(metric), // original JSON format
);
}
}
send(data); // send to an endpoint different from Prometheus server
I think I need to implement custom encoder, translate metrics to JSON format.
Please let me know if there is any good way. 🙏
let total_bytes: Family<TrafficLabels, Counter> = Family::default();
let connection_count: Family<ConnectionLabels, Gauge> = Family::default();
let mut registry = Registry::default();
registry.register(
"total_bytes",
"Accumulated bytes going through sever",
Box::new(total_bytes.clone()),
);
registry.register(
"connection count",
"Count of current connections",
Box::new(connection_count.clone()),
);
results in:
mismatched types
expected struct `Family<TrafficLabels, prometheus_client::metrics::counter::Counter, fn() -> prometheus_client::metrics::counter::Counter>`
found struct `Family<ConnectionLabels, prometheus_client::metrics::gauge::Gauge, fn() -> prometheus_client::metrics::gauge::Gauge>`rustc[E0308](https://doc.rust-lang.org/error-index.html#E0308)
There is a relationship between OpenMetrics and Prometheus, but as far as I understand, they are not the same thing, and are definitely not fully compatible with each other.
lib.rs currently states that this repo is a "Client library implementation of the Open Metrics specification".
If that's true, then I propose that this repo should not exist underneath the prometheus GitHub organization. If it will remain in the prometheus org, then I propose that it should change its mandate such that Prometheus compatibility is the first-order goal, and OpenMetrics is a secondary and e.g. nice-to-have goal.
Histogram metric missed default implementation,wo can add default buckets.
Is there an elegant way of combining multiple registries in a single encode output?
An example of a use case would be writing an exporter for metrics from an external system, as opposed to the own application's metrics.
The idea is that new metric families would be created and populated on each scrape. But there could also be exporter-related metrics that would be constant between scrapes, such as an info, etc.
One way of doing this right now would be to register the exporter's metrics to a new, per-scrape Registry, together with the scrape metrics and encode that. This approach seems somewhat verbose.
A more ergonomic approach would be for the encode function to take some iterable of Families and encode that, which is basically what the Registry is from its point of view.
Hi guys, thanks for your contribution!
There is an issue with using the client with actix-web framework.
Because Registry
doesn't implement Copy
- the only working solution I've found is to wrap it with Mutex
. But it requires locking at least on each collecting of metrics which isn't good for performance. Are there any other suggestions?
I may be missing something but it seems like it's not possible to use the Registry
with the default M = SendEncodeMetric
type as shared state in web frameworks (e.g. tide::with_state
), because those require that the state is all Sync
but SendEncodeMetric
is not Sync
. If I update the tide
example to use a dynamically dispatched Registry
then it fails to compile:
✦ ❯ cargo c --example tide
error[E0277]: `(dyn SendEncodeMetric + 'static)` cannot be shared between threads safely
--> examples/tide.rs:25:36
|
25 | let mut app = tide::with_state(State {
| ___________________----------------_^
| | |
| | required by a bound introduced by this call
26 | | registry: Arc::new(registry),
27 | | });
| |_____^ `(dyn SendEncodeMetric + 'static)` cannot be shared between threads safely
|
= help: the trait `Sync` is not implemented for `(dyn SendEncodeMetric + 'static)`
= note: required because of the requirements on the impl of `Sync` for `Unique<(dyn SendEncodeMetric + 'static)>`
= note: required because it appears within the type `Box<(dyn SendEncodeMetric + 'static)>`
= note: required because it appears within the type `(Descriptor, Box<(dyn SendEncodeMetric + 'static)>)`
= note: required because of the requirements on the impl of `Sync` for `Unique<(Descriptor, Box<(dyn SendEncodeMetric + 'static)>)>`
= note: required because it appears within the type `alloc::raw_vec::RawVec<(Descriptor, Box<(dyn SendEncodeMetric + 'static)>)>`
= note: required because it appears within the type `Vec<(Descriptor, Box<(dyn SendEncodeMetric + 'static)>)>`
= note: required because it appears within the type `Registry`
= note: required because of the requirements on the impl of `std::marker::Send` for `Arc<Registry>`
note: required because it appears within the type `State`
--> examples/tide.rs:59:8
|
59 | struct State {
| ^^^^^
note: required by a bound in `with_state`
--> /Users/ben/.cargo/registry/src/github.com-1ecc6299db9ec823/tide-0.16.0/src/lib.rs:153:20
|
153 | State: Clone + Send + Sync + 'static,
| ^^^^ required by this bound in `with_state`
<several related errors omitted>
It's fixed by adding a Sync
bound to SendEncodeMetric
but it feels like this is a common use case so I imagine it's come up before and I'm doing something silly - am I?
Thanks!
Currently when deriving EncodeLabelSet
and flattening a struct the flattened struct must appear last, and there must be only one flattened struct, from the test:
#[test]
fn flatten() {
#[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)]
struct CommonLabels {
a: u64,
b: u64,
}
#[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)]
struct Labels {
unique: u64,
#[prometheus(flatten)]
common: CommonLabels,
}
// …
If you place common
before unique
in struct Labels
like this:
#[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)]
struct Labels {
#[prometheus(flatten)]
common: CommonLabels,
unique: u64,
}
Compilation fails:
error[E0382]: borrow of moved value: `encoder`
--> derive-encode/tests/lib.rs:147:14
|
147 | #[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)]
| ^^^^^^^^^^^^^^
| |
| value borrowed here after move
| move occurs because `encoder` has type `LabelSetEncoder<'_>`, which does not implement the `Copy` trait
|
This occurs because ownership of encoder
is given to the flattened struct
I think this would be a breaking change to fix, but fixing it would allow fields to appear in any order, or allow flattening of multiple structs without nesting them all one inside the other in tail position.
As the title says, is it okay to add a constructor (with_prefix_and_labels
; please suggest a better name eventually) that allows the creation of a base/root registry with a prefix and some default labels? Or maybe a way to allow the mutation of the labels of a registry.
I have a bunch of counters coming from from processes running on different hosts and I'd like to have them labeled with about 2-3 base/identification labels to be able to query them in my dashboards properly. Due to some restrictions I am not able to inject these labels directly via kubernetes on Prometheus scrape. At the same time, I see no reason TO NOT allow the creation of a registry with default labels.
This is a question related a bit to the previously posted issue (#144) for which I submitted a pull request (#145).
The output or encode()
is non-deterministic when creating multiple metrics of the same type.
So basically:
pub fn repro() -> String {
let mut registry = <prometheus_client::registry::Registry>::default();
let gauge = Family::<MyLabels, Gauge<f64, AtomicU64>>::default();
registry.register("test", "help", gauge.clone());
gauge
.get_or_create(&MyLabels {
label: "one".into(),
})
.set(0.1);
gauge
.get_or_create(&MyLabels {
label: "two".into(),
})
.set(0.2);
let mut buffer = String::new();
encode(&mut buffer, ®istry).unwrap();
buffer
}
This will sometimes return this:
HELP test help.
# TYPE test gauge
test{label="one"} 0.1
test{label="two"} 0.2
# EOF
And sometimes this:
HELP test help.
# TYPE test gauge
test{label="two"} 0.2
test{label="one"} 0.1
# EOF
I would expect the metrics to be somehow sorted deterministically.
Sometimes I have already e.g. nodes: Vec<Node>
in the application state where Node
is like
struct Node {
url: String,
requests: AtomicU64,
}
(When you are already working with a &Node
, bumping the requests counter is just an atomic inc. It's much more efficient than get_or_create(...).inc()
.)
And I'd like to expose the requests counter of all nodes as a metric family. It might be possible with the collector API, but it would still require quite some boxing, cloning and iterator mapping.
Instead I'd like to encode the metrics myself, e.g.:
encode_descriptor(&mut buf, ...);
for n in &ctx.nodes {
encode_counter_with_labels(&mut buf, [("node", &n.url)], n.requests.load(...));
}
encode_eof(&mut buf);
This will not require any extra allocation or cloning. By fusing the collecting and encoding phase, metrics can be encoded very efficiently.
Hello folks,
Looking at the example code in lib.rs (when rendered on docs.rs), it was a little confusing to figure out what was going on due to many functions and types from the client_rust library being imported but the imports themselves being hidden.
What do you think about un-hiding the library imports so that it's more clear which parts of the client_rust library the example code is referencing?
Hi! I've been looking at implementing a Prometheus collector for the recently announced tokio-metrics crate. Every scrape, I'd like to gather runtime metrics for the currently Tokio runtime. The problem is that doing so requires a non-trivial amount of up-front work to aggregate all of the stats across the N workers in the runtime, which I'd rather not do during every metric's encode
function (following the custom metric example).
Instead I think it'd be ideal if there was a way to do something similar to the client_python Custom Collector example, which allows custom collectors to record values for multiple metrics at each scrape time - that'd avoid me having to duplicate work (non-atomically) on every scrape. Do you think such an API would be possible?
Alternatively if you know of another pattern to get around this, I'd love to hear it!
Currently Counter's API is:
https://github.com/prometheus/client_rust/blob/master/src/metrics/counter.rs#L76-L83
/// Increase the [`Counter`] by 1, returning the previous value.
pub fn inc(&self) -> N {
self.value.inc()
}
/// Increase the [`Counter`] by `v`, returning the previous value.
pub fn inc_by(&self, v: N) -> N {
self.value.inc_by(v)
}
which requires inc
and inc_by
to return the previous value. This is rarely needed for metric collecting use-case, and it hinders the ability to use per-thread thread-local accumulator to prevent contention (like this) and increase performance.
All the fields present in the Prometheus Descriptor are private which doesn't allow to initialise a new desc for custom metric. Should there be a default or parametrized constructor?
Let's say, I want to create a collector which contains different metrics and those metrics should have a new desc. How can I create one? Is the Descriptor concept fully implemented in this crate?
Similar to this piece of code: https://github.com/tikv/rust-prometheus/blob/ac86a264223c8d918a43e739ca3c48bb4aaedb90/src/desc.rs#L86
Hello,
I recently stumbled upon tikv/rust-prometheus#392 and now I'm preparing a migration from rust-prometheus
. I'm wondering how it would be possible to add a collector for process metrics just like what rust-prometheus
does currently. I'm really inexperienced in those packages, and I don't really see how the ProcessCollector
thing would translate here, to add some metrics related to the running process.
Can you tell me what would be necessary to add support for this ? I was thinking probably as another crate that exposes a single global function like crate::export_process_metrics()
; so that crate would have to add timers to run the collection and hope that the timer runs often enough to give a precise measurement when prometheus scrapes the endpoint ?
Regards,
Gerry
Hi,
Thanks for creating an official Rust crate for Prometheus. :)
I'm wondering how we should handle Const Metrics with this crate. Are these supported at the moment? If they are, it's a little non-obvious.
My use case is for exporting counters from the operating system. The OS itself guarantees that these are always increasing (unless the counter wraps, etc), but they're difficult to export with only the inc
and inc_by
methods as additional variables must be maintained to work out the difference between the old OS counter and the current OS counter, so we can finally inc_by
.
If Const Metrics are not currently supported, please consider this a feature request.
Thanks!
As discussed in #63, we should upgrade to Rust edition 2021.
Tracking this here for the sake of completeness.
//CC @doehyunbaek
Prometheus now supports a new "Native Histograms" datatype. We should implement this here.
Serde's Serialize trait is implemented for dozens of types in the Rust ecosystem, its derive code is fairly well optimised, and it is the most popular serialization framework we have for Rust.
What do you think of removing the Encode trait in favour of Serialize, to get all the nice things there are in Serde for free?
One oftentimes has a struct
of metrics like:
struct Metrics {
a: Counter,
b: Gauge,
}
Code to register the metrics in Metrics
with a Registry
could be generated via a derive macro on Metrics
.
#[derive(Register)]
struct Metrics {
/// My Gauge tracking the number of xxx
a: Counter,
/// My Counter, tracking the event xxx
#[prometheus-unit(Seconds)]
b: Gauge,
}
Where the Register
trait is something along the lines of:
trait Register {
fn create_and_register(registry: &mut Registry) -> Self;
}
Going through all repositories right now, I saw that this repository has no MAINTAINERS.md.
It would be great to create one for visibility.
It would be super useful to have derives for metrics storages, like was done for the prometheus
crate (essentially porting https://crates.io/crates/prometheus-metric-storage).
#[derive(prometheus_metric_storage::MetricStorage)]
#[metric(subsystem = "transport", labels("endpoint"))]
struct Metrics {
/// Number of requests that are currently inflight.
inflight: prometheus::IntGauge,
/// Number of finished requests by response code.
#[metric(labels("status"))]
requests_finished: prometheus::IntCounterVec,
/// Number of finished requests by total processing duration.
#[metric(buckets(0.1, 0.2, 0.5, 1, 2, 4, 8))]
requests_duration_seconds: prometheus::Histogram,
}
fn main() {
let metrics = Metrics::new(
prometheus::default_registry(),
/* endpoint = */ "0.0.0.0:8080"
).unwrap();
metrics.inflight.inc();
metrics.requests_finished.with_label_values(&["200"]).inc();
metrics.requests_duration_seconds.observe(0.015);
}
This would enable to not forget to register metrics that are in a struct into the registry.
Today, one can create a sub-registry with a metric name prefix:
We should support creating sub-registries either by metric name or constant label(s).
Shouldn't help strings and label values be escaped?
https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#escaping
For systems where there are many components which need to register their own metrics, shared mutable access to the registry is required in order to keep things nice and simple (for the user). Providing a built-in solution for this pattern in the crate would lower the barrier of adoption, and could also help folks not to mess up their own implementation of the pattern where they might code the mutex incorrectly.
The prometheus crate has such a pattern, and it definitely makes getting started quite easy.
Because of rust-lang/rust#77143, the impl Add<&Prefix> for String
can prevent the compilation of unrelated, previously working programs, if they use string concat. This prevents adding this library to such a project, or prevents adding other deps to a project that already uses prometheus. The resulting error message is also cryptict.
Please consider removing the trait impl, thanks.
Some metrics need to be created ad-hoc on scrape, e.g. a counter retrieved from another system.
Help strings should end with a period, per Prometheus guidance. But if a user does that themselves, the help string will be incorrect, as "a full stop punctuation mark (.) is automatically added to the passed help text" given to Registry::register.
Probably the best solution here is to append the period only if it doesn't already exist.
Looking for some advice on feeding the metrics stored in a Registry
to a prometheus instance, using https://crates.io/crates/prometheus. I could add an example to the repo after I get it working.
Thanks!
I tried using ()
as the label set for a family, like Family<(), Histogram, ...>
. Unfortunately this resulted in the following line in my metrics output:
histogram_name_bucket{,le="1.0"} 44
This results in a expected label name, got "COMMA"
error when Prometheus tries to collect the metrics.
I would expect this to "just work" and just not insert any label (i.e. basically not have the leading comma in {,le="1.0"}
)
Code like e.g. this suggests that this crate permits metric names containing spaces. The data model asserts that
The metric name. . . may contain ASCII letters and digits, as well as underscores and colons. It must match the regex
[a-zA-Z_:][a-zA-Z0-9_:]*
.
Names that don't meet these criteria should be rejected.
Given the structs like:
struct Common {
field_a: String,
field_b: String,
field_c: String
}
#[derive(Encode)]
struct Metric1 {
unique_field: String
#[flatten]
common: Common
}
#[derive(Encode)]
struct Metric2 {
unique_field_2: String
#[serde(flatten)]
common: Common
}
I want the resulting metrics:
metric1{field_a="foo", field_b="bar", field_c="baz",unique_field="something"}
metric2{field_a="foo", field_b="bar", field_c="baz",unique_field_2="something_else"}
(Note: in real world Common and the number of metrics are much larger)
This would match the semantics of serde(flatten)
See #105 (comment) for more discussion
Owning ref is (1) seemingly unmaintained since last commit was 2 years ago and (2) unsound. Something else should probably be used.
Currently the encoder only supports a single metric. There is no concept like metrics lists.
It's not uncommon that collectors get raw data from their source and generate a list of metrics from that.
Right now it seems like we have to register every metric, but it would be great if we had a way to generate metrics on the fly on every scrape and pass them to the encoder.
Or am I just missing the right module?
the rust client library doesn't provide the implementation of exposing promethues metrics.
It would be terrific to be able to pass an optional chrono instance as a timestamp to specify the recency of the particular metric. My usecase is a prometheus exporter where the metrics are cached and it would therefore be correct to specify the time of the original retrieval of the metric instead of leaving out the timestamp which implicitly states "now".
The problem is demonstrated by the following comment —
//! let expected = "# HELP my_counter This is my counter.\n".to_owned() +
//! "# TYPE my_counter counter\n" +
//! "my_counter_total 1\n" +
//! "# EOF\n";
The HELP
and TYPE
lines should use my_counter_total
, not my_counter
.
Hi! My team at work is converting some of our projects over from docs.rs/prometheus to this crate. One nice-to-have feature we miss from the old crate is the HistogramTimer type. I suggest adding it into this crate for two reasons:
Happy to implement this myself.
There is no way to copy/clone the HashMap<S,M>
within family.
There is no way to iterate over a metric Family
or get metrics without knowing the labels.
get_or_create()
for every metric. This isn't great if you don't know the label names (don't store them etc...).I am happy to tackle these if you want. I think making M
clone (not sure why it isn't atm) and returning a new hashmap with the cloned values would fix both these problems.
With the diff below, one would not need to Box
a metric before registering it with a Registry<Box<M>>
. The downside is, that it makes it harder for Rust to infer M
.
diff --git a/src/lib.rs b/src/lib.rs
index d39b566..d86d2dd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -52,7 +52,7 @@
//! "http_requests",
//! // And the metric help text.
//! "Number of HTTP requests received",
-//! Box::new(http_requests.clone()),
+//! http_requests.clone(),
//! );
//!
//! // Somewhere in your business logic record a single HTTP GET request.
diff --git a/src/registry.rs b/src/registry.rs
index be9fd18..3033483 100644
--- a/src/registry.rs
+++ b/src/registry.rs
@@ -97,8 +97,8 @@ impl<M> Registry<M> {
///
/// registry.register("my_counter", "This is my counter", counter.clone());
/// ```
- pub fn register<N: Into<String>, H: Into<String>>(&mut self, name: N, help: H, metric: M) {
- self.priv_register(name, help, metric, None)
+ pub fn register<N: Into<String>, H: Into<String>, IM: Into<M>>(&mut self, name: N, help: H, metric: IM) {
+ self.priv_register(name, help, metric.into(), None)
}
/// Register a metric with the [`Registry`] specifying the metric's unit.
Hey
I have a bunch of counters coming from from processes running on different hosts and I'd like to have them labeled with about 2-3 labels to be able to query them in my dashboards.
Is there a reason sub_registry_with_label
accepts a single label? What if I want to add 2-3 labels to a bunch of metrics (globally)?
Hi all, thanks for making this library, I'm really excited to get started with exemplars. But I can't see how to combine exemplars and families.
Problem
By my read of the OpenMetrics spec, I should be able to export metrics like this:
latency_bucket{result="success",le="0.00256"} 27300 # {trace_id="3a2f90c9f80b894f"} 0.001345422
Note that the latency
metric has labels in both its family (result
) and its exemplars (trace_id
)
I am trying to implement that using this library, but it won't compile. Why? Because the trait TypedMetric
is not implemented for CounterWithExemplar<ExemplarLabel>
and that means my metric does not impl SendSyncEncodeMetric
. So it can't be registered. See this example:
use prometheus_client::{
encoding::text::SendSyncEncodeMetric,
metrics::{exemplar::HistogramWithExemplars, family::Family, histogram::exponential_buckets},
registry::Registry,
};
use prometheus_client_derive_encode::Encode;
fn main() {
// Create a metric registry.
let mut registry = <Registry>::default();
// Metric will be used something like this:
// latency_bucket{result="success",le="0.00256"} 27300 # {trace_id="3a2f90c9f80b894f"} 0.001345422 1.6188203823429482e+09
let latency: Family<ResultLabel, HistogramWithExemplars<TraceLabel>> =
Family::new_with_constructor(|| {
HistogramWithExemplars::new(exponential_buckets(1.0, 2.0, 10))
});
// This line doesn't compile:
//
// the trait `TypedMetric` is not implemented for `HistogramWithExemplar<TraceLabel>`
let b: Box<dyn SendSyncEncodeMetric> = Box::new(latency);
// Register metrics.
registry.register("latency", "help text goes here", b);
}
#[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)]
pub struct ResultLabel {
pub result: String,
}
#[derive(Clone, Hash, PartialEq, Eq, Encode, Debug, Default)]
pub struct TraceLabel {
trace_id: String,
}
Consider making Descriptor::new
const
. This would be handy when used with Collector
when passing a borrowed 'static
`Descriptor.
As far as I can tell, the only current blocker is the string manipulation to add a ,
:
Lines 437 to 438 in 85033bf
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.