GithubHelp home page GithubHelp logo

lexifi / landmarks Goto Github PK

View Code? Open in Web Editor NEW
128.0 13.0 20.0 1.05 MB

A Simple Profiling Library for OCaml

License: MIT License

OCaml 98.54% C 0.79% JavaScript 0.68%
ocaml profiling-library ppx-extension callgraph landmark

landmarks's Introduction

Landmarks: A Simple Profiling Library

Landmarks is a simple profiling library for OCaml. It provides primitives to delimit portions of code and measure the performance of instrumented code at runtime. The available measures are obtained by aggregating CPU cycles (using the cpu's time stamp counter), applicative time (using Sys.time) and allocated bytes (with Gc.allocated_bytes). The instrumentation of the code may either done by hand, automatically or semi-automatically using a PPX extension.

During the execution of your program, the traversal of instrumented code by the control flow is recorded as a "callgraph" that carries the collected measures. The results may be browsed either directly on the console, or exported to json.

This tool is intended to be used as a way to find where the time is spent in your programs (and not benchmark independent pieces of code like Core_bench) while providing results that only correspond to the instrumented portion of your OCaml code (contrary to tools that directly work with the binary executable like gprof or perf).

For more information, you may browse the API.

Installation

The library is split into two packages: landmarks for the runtime library and landmarks-ppx for the preprocessor implementing automatic instrumentation.

  • Using opam:
opam install landmarks

or

opam install landmarks-ppx

will install either the runtime library or both the runtime library and the preprocessor.

  • Manually:
git clone https://github.com/LexiFi/landmarks.git
cd landmarks
dune build @install

Usage with dune

Direct Method

Simply use the library landmarks and the preprocessor landmarks-ppx to benchmark your executables and libraries. For instance, the following dune file builds the executable test using the landmarks library and its PPX. The optional --auto flag turns on the automatic instrumentation (see below).

(executable
 (name test)
 (libraries landmarks)
 (preprocess (pps landmarks-ppx --auto))
)

Using dune's instrumentation

It is possible to use dune to automatically trigger the instrumentation of a project. Have a look at LexiFi/landmarks-starter for a basic example and see the dune manual for more information.

Terminal Video of commands describe below

Usage with ocamlfind

  • Compiling and linking:
  ocamlfind ocamlopt -c -package landmarks prog.ml
  ocamlfind ocamlopt -o prog -package landmarks -linkpkg prog.cmx

You can replace "ocamlopt" by "ocamlc" to compile the program in bytecode.

  • With the PPX extension:
  ocamlfind ocamlopt -c -package landmarks -package landmarks-ppx -ppxopt landmarks-ppx,--auto prog.ml
  ocamlfind ocamlopt -o prog -package landmarks -linkpkg prog.cmx

Note that "-ppxopt landmarks-ppx,--auto" is optional and turns on the automatic instrumentation.

Benchmarking manually

There are three main primitives:

  val register: string -> landmark
  val enter: landmark -> unit
  val exit: landmark -> unit

The register function declares new landmarks and should be used at the toplevel. The functions enter and exit are used to delimit the portion of code attached to a landmark. At the end of the profiling, we retrieve for each landmark the aggregated time information spent executing the corresponding piece of code. During the execution, a trace of each visited landmark is also recorded in order to build a "callgraph".

For example:

open Landmark

let loop = register "loop"
let sleep = register "sleep"
let main = register "main"

let zzz () =
  enter sleep;
    Unix.sleep 1;
  exit sleep

let () =
  begin
    start_profiling ();
    enter main;
      enter loop;
        for _ = 1 to 9 do
          zzz ()
        done;
      exit loop;
      zzz ();
    exit main;
  end

(This file can be compiled with ocamlfind ocamlc -o prog -package landmarks -package unix -linkpkg prog.ml)

The induced callgraph is:

- 100.00% : main
|   - 90.00% : loop
|   |   - 100.00% : sleep
|   - 10.00% : sleep

which can be paraphrased as:

  • 100% of time is spent inside the main landmark,
  • 90% of time spent inside the main landmark is spent in the loop landmark,
  • 10% of time spent inside the main landmark is spent in the sleep landmark,
  • 100% of the time spent in loop is spent in the sleep landmark.

The clock() function

The library provides a binding to the high-performance cycles counter for x86 32 and 64 bits architectures (note that you may use the landmarks-noc.cm(x)a archive to provide your own implementation). It is used to measure the time spent inside instrumented code.

The PPX extension point

To avoid writing boilerplate code, you may use the ppx extension distributed with this package. It allows the programmer to instrument expressions using annotation and to automatically instrument top-level functions.

Annotations

The value expr [@landmark "name"] is expanded into

  Landmark.enter __generated_landmark_1;
  let r =
    try expr with e -> Landmark.exit __generated_landmark_1; raise e
  in
  Landmark.exit __generated_landmark_1;
  r

and the declaration

  let __generated_landmark_1 = Landmark.register "name"

is appended at the top-level.

It should be pointed out that this transformation does not preserve tail-recursive calls (and also prevents some polymorphism generalization). To get around these problems, it is recommended to use the other provided extension around let ... in and let rec ... in:

let[@landmark] f = body

which is expanded in :

let __generated_landmark_2 = Landmark.register "f"
let f = body
let f x1 ... xn =
  Landmark.enter __generated_landmark_2;
  let r =
    try f x1 ... xn with e -> Landmark.exit __generated_landmark_2; raise e
  in
  Landmark.exit __generated_landmark_2;
  r

when the arity n of f is obtained by counting the shallow occurrences of fun ... -> and function ... -> in body. Please note that when using this annotation with let-rec bindings, only entry-point calls will be recorded. For instance, in the following piece of code

  let () =
    let[@landmark] rec even n = (n = 0) || odd (n - 1)
    and[@landmark] odd n = (n = 1) || n > 0 && even (n - 1)
    in Printf.printf "'six is even' is %b\n" (even 6)

the landmark associated with "even" will be traversed exactly once (and not three times !) whereas the control flow will not pass through the landmark associated with "odd".

Automatic instrumentation

The structure annotations [@@@landmark "auto"] and [@@@landmark "auto-off"] activate or deactivate the automatic instrumentation of top-level functions in a module. In automatic mode, all functions declarations are implicitly annotated. Automatic instrumentation can be enabled/disabled for all files via option auto in OCAML_LANDMARKS, as detailed below.

The OCAML_LANDMARKS environment variable

The environment variable OCAML_LANDMARKS is read at two different stages: when the ppx rewriter is executed, and when the landmarks module is loaded by an instrumented program. This variable is parsed as a comma-separated list of items of the form option=argument or option, where option is:

  • During the ppx rewriter stage (at compilation time):

    • auto (no arguments): turns on the automatic instrumentation by default (behaves as if each module starts with annotation [@@@landmark "auto"]).

    • threads (no arguments): tells the ppx extension to use the Landmark_threads module instead of the module Landmark.

  • When loading an instrumented program (at runtime):

    • format with possible arguments: textual (default) or json. It controls the output format of the profiling which is either a console friendly representation or json encoding of the callgraph.

    • threshold with a number between 0.0 and 100.0 as argument (default: 1.0). If the threshold is not zero the textual output will hide nodes in the callgraph below this threshold (in percent of time of their parent). This option is meaningless for other formats.

    • output with possible argument: stderr (default), stdout, temporary, <file> (where <file> is the path of a file). It tells where to output the results of the profiling. With temporary it will print it in a temporary file (the name of this file will be printed on the standard error). You may also use temporary:<directory> to specify the directory where the files are generated.

    • debug with no argument. Activates a verbose mode that outputs traces on stderr each time the landmarks primitives are called.

    • time with no argument. Also collect Sys.time timestamps during profiling.

    • off with no argument. Disable profiling.

    • on with no argument. Enable profiling (default; may be omitted).

    • allocation with no argument. Also collect Gc.allocated_byte data.

Browsing the JSON export using the Web Viewer

You can either compile the web viewer on your computer or browse it online. You need to load the JSON files using the filepicker and then you can click around to browse the callgraph.

Instrumenting with threads

The Landmark module is not thread-safe. If you have multiple threads, you have to make sure that at most one thread is executing instrumented code. For that you may use the Landmark_threads module (included in the landmarks-threads.cm(x)a archive) that prevents non thread-safe functions to execute in all threads but the one which started the profiling.

Known Issue

The annotation on expressions may temper with polymorphism (this is not the case for the let-binding annotation). For instance, the following piece of code will fail to compile:

  let test = (fun x -> x)[@landmark "test"]
  in test "string", test 1

About

This 'Landmarks' package is licensed by LexiFi under the terms of the MIT license.

Contact: [email protected]

landmarks's People

Contributors

arbipher avatar johnyob avatar leostera avatar maiste avatar maroneze avatar mefyl avatar mlasson avatar nojb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

landmarks's Issues

ppx dependencies woes; package split?

I am considering using Landmarks for a project, but opam install landmarks wants to downgrade and recompile ppxlib, which is a red flag as most of the OCaml ppx ecosystem depends on ppxlib nowadays (I don't want to recompile all those dependencies, and I should not have to downgrade).

I think that there are two related problems here:

  1. Landmarks has a strict dependency on ocaml-migrate-parsetree < 2.0.0; could this requirement be lifted in a new version of landmarks, that would make it compatible with the rest of the still-alive ppx ecosystem?

  2. I don't actually plan to use landmarks ppx support, so I would prefer being able to install landmarks without dealing with all of that. Could the ppx support be proposed in a separate package landmarks-ppx, so that non-ppx users can easily install the software without having to pay for ppx maintainability woes?

I would be willing to help along the way, but not to do (2), which sounds easier but is also very boring packaging stuff. @mlasson, if you were motivated to implement (2), I could have a try at a PR to fix (1) in reciprocity -- unless this has already been done somewhere and I missed it, of course.

broken viewer?

It seems that the online viewer cannot read the generated JSON files anymore. Is this expected? Thanks for the nice tool :-)

Benchmarking js_of_ocaml library with `auto` only times constructor

I have been trying to use the new compatibility with js_of_ocaml added in #36.

I have my code organized into a library with the final part of my code being

let () =
  Js.export "somefun" some_function

This is then called in javascript with

var lib = require('./mylib.bc.js');
lib.some_function()

and I run the whole thing with OCAML_LANDMARKS="format=textual" node test.js

Landmarks does produce output, but it seems that it only profiles the setup code being run, not the eventual use of some_function. If I put a print statement in some_function, it is printed after the output of Landmarks, which is full of timings for loading different modules in my code.

Release a ppxlib compatible version

Since the main branch has already been ported to ppxlib it would be great if you could cut a release, so that landmarks can be used together with other PPXs in their latest version.

Support browser's profile viewers json formats

Speaking of viewers, web browsers come with their own profile viewers. Maybe you could simply piggyback on these.

For Chrome this happens at the chrome://tracing address and the JSON file format it can read is described here.

For Firefox it's in the browser's webdeveloper performance tools or here. The format seems to be a little bit less well documented but some type defintions can be found here.

Viewer ?

Hello,

The online viewer is a 404 and the package landmarks-viewer mentioned in the README doesn't exist.

Weird counters for load(opcodes.ml) in the sample typecore.json

The provided sample typecore.json shows:

load(bytecomp/opcodes.ml)   30 396  151 744 080

i.e. 30396 calls and 151744080 CPU cycles. I've been told (personal communication) that this landmark wraps the initialization for that compilation unit, so it should really be called at most once in a given process (as other load(...) landmark).

Support for `arm64` / Apple M1

Thank you for the incredibly useful landmarks library! It works flawlessly for us on x86, but we seem to be out of luck for now on our Apple M1s. Is there a nontrivial technical obstruction for supporting arm64? If there is anything we could do to help, we would be happy to try.

Error with let operators

We're using Landmarks on Frama-C (when landmarks is installed, it is optionally enabled when building Frama-C), but a recent addition of a let+ operator in our code results in an error:

Fatal error: exception (...): let operators are not supported before OCaml 4.08
Error: Error while running external preprocessor
Command line: /home/user/.opam/4.08.1/lib/landmarks/ppx/./ppx.exe --as-ppx '/tmp/camlppx2353e9' '/tmp/camlppxf2433e'

I tried using OCaml 4.08, but also 4.12, and I get the same error message mentioning OCaml < 4.08, so I'm assuming it's an issue with the error message and not my OCaml version.

I believe that fixing #26 would fix this as well, but in case there's a simpler workaround for this issue in particular, it would be helpful. For now, we have to disable landmarks to be able to compile Frama-C.

Building with dune produces not working program

I tried to build the following example:

open Landmark

let loop = register "loop"
let sleep = register "sleep"
let main = register "main"

let zzz () =
  enter sleep;
    Unix.sleep 1;
  exit sleep

let () =
  begin
    start_profiling ();
    enter main;
      enter loop;
        for _ = 1 to 9 do
          zzz ()
        done;
      exit loop;
      zzz ();
    exit main;
  end

With a dune file like:

(executable
 (name main)
 (libraries landmarks unix)
 (modes native)
 (preprocess (pps landmarks.ppx --auto))
)

Unfortunately when I run dune build from the project's root directory the generated program (main.exe) returns the following output:

Warning: landmark failure when closing 'load(bin/main)' (bin/main.ml:1), expecting 'ROOT' (src/landmark.ml).
Call graph './main.exe':
------------------------

Note: Nodes accounting for less than 1.00% of their parent have been ignored.

Aggregated table:
----------------
Name;        Filename;    Calls;     Time; Allocated bytes
ROOT; src/landmark.ml;        0;   69.01K;    59888
Fatal error: exception Landmark.LandmarkFailure("unable to recover from landmark failure when closing 'load(bin/main)' (bin/main.ml:1), expecting 'ROOT' (src/landmark.ml).")

Having said that, building the same program with ocamlfind ocamlc -o prog -package landmarks -package unix -linkpkg main.ml results with perfectly fine working program that produces the following output:

Call graph './prog':
--------------------
[   22.61G cycles in 1 calls ]     - 100.00% : main
[   20.35G cycles in 1 calls ]     |   - 90.00% : loop
[   20.35G cycles in 9 calls ]     |   |   - 100.00% : �[1;31msleep�[0m
[    2.26G cycles in 1 calls ]     |   - 10.00% : �[0;31msleep�[0m

Note: Nodes accounting for less than 1.00% of their parent have been ignored.

Aggregated table:
----------------
 Name;        Filename;    Calls;     Time; Allocated bytes
 ROOT; src/landmark.ml;        0;   22.61G;   113304
 main;         unknown;        1;   22.61G;     7456
sleep;         unknown;       10;   22.61G;     2720
 loop;         unknown;        1;   20.35G;     5472

Automatic instrumentation fails with labelled arguments `x1`

Instrumenting

let in_box ~x1 c =
  x1 <= c.x1

produces

    let in_box ~x1  x1 =
      Landmark.enter __generated_landmark_a80eafc94c19d07eaf47a4752adba800_1;
      (let r =
         try (in_box ~x1) x1
         with
     ....

(clash between the generated argument x1 and the label)

Errors with some OCAML_LANDMARKS options

I tried both the OPAM 1.0 version and git-pinning as indicated, and got the same result in both cases.

After compiling the example program, I tried several options, but got some errors:

$ OCAML_LANDMARKS=debug ./prog
[LANDMARKS] Unknown option 'debug'.

$ OCAML_LANDMARKS=on ./prog
[LANDMARKS] Unknown option 'on'.

$ OCAML_LANDMARKS=output=/tmp/file.txt ./prog
[LANDMARKS] The argument '/tmp/file.txt' in not valid for the option 'output'.

$ OCAML_LANDMARKS=output=temporary:/tmp/ ./prog
[LANDMARKS] The argument 'temporary:/tmp/' in not valid for the option 'output'.

This also happens when adding simple and double quotes to the names of the file or directory in the latter cases.

The other options I tried (off, format=json, auto, etc.) all seem to work, so I don't know if I'm doing something wrong.

Clarification about recursive call count

I'd like to suggest a clarification about recursive function calls to the Github README page (which I find very useful as reference for new users): I think it could be useful to add something related to the call counters, e.g. that they may not correspond to the "intuitive" notion when using PPX-generated landmarks inside recursive call nests.

For instance, in the following program:

let[@landmark] rec f n =
  if n <= 1 then 1 else g (n-1)
and[@landmark] g n =
  f (n-1)

let[@landmark] rec h n =
  if n <= 1 then 1 else h (n-1)

let () =
  Format.printf "g = %d@." (g 10);
  Format.printf "h = %d@." (h 10);

The report says that functions g and h were both called once (1 calls), and f seems to not have been called.

This is not very intuitive (I'd have expected all functions to have several calls each) and can lead to some confusion. For instance, in my real-world case, I had a mutually recursive function which was not directly called, so it was not being reported, even though the function was indirectly called by another one. A small clarification about that could help beginners.

Automatic instrumentation via landmarks-ppx and inherited constructors without module name

This is a simplified example of an issue that happened with Frama-C's visitors:

(* a.ml *)
type visitAction = Go | Skip
class visitor = object
  method visit () = Go
end
(* b.ml *)
class skip_visitor = object
  inherit A.visitor
  method! visit () = Skip (* Could be A.Skip, but due to inheritance, the module name is unnecessary *)
end
OCAML_LANDMARKS=auto ocamlfind ocamlopt -package landmarks -package landmarks-ppx a.ml b.ml

This fails with:

File "b.ml", line 4, characters 21-25:
4 |   method! visit () = Skip
                         ^^^^
Error: Unbound constructor Skip

Without Landmarks instrumentation, the above compiles, since the Skip constructor is found in module A.

If I explicitly write A.Skip in b.ml, it works fine.

If this is hard to fix, I believe a small addition to the Known issues section with the workaround should suffice to help people find it. Classes and inheritance are rarely used anyway in OCaml.

Compilation in OCaml 4.04.1

I tried compiling landmarks on the freshly-released OCaml 4.04.1, but I got the following error when trying opam install landmarks (I made a local opam repository using the extracted contents of the OCaml release archive):

[ERROR] The compilation of landmarks failed at "make".
Processing  1/1: [landmarks: ocamlfind remove]
#=== ERROR while installing landmarks.1.1 =====================================#
# opam-version 1.2.2
# os           linux
# command      make
# path         /home/andr/.opam/4.04.1+local-git-/build/landmarks.1.1
# compiler     4.04.1+local-git-
# exit-code    2
# env-file     /home/andr/.opam/4.04.1+local-git-/build/landmarks.1.1/landmarks-21719-5f1023.env
# stdout-file  /home/andr/.opam/4.04.1+local-git-/build/landmarks.1.1/landmarks-21719-5f1023.out
# stderr-file  /home/andr/.opam/4.04.1+local-git-/build/landmarks.1.1/landmarks-21719-5f1023.err
### stdout ###
# ocamldep -I . *.mli *.ml > .depend
# ocamlopt  -c clock.c
# ocamlc -w +A-30-42-41-48-40-4 -safe-string -strict-sequence -bin-annot -g -c landmark_misc.ml
# ocamlc -w +A-30-42-41-48-40-4 -safe-string -strict-sequence -bin-annot -g -c landmark_graph.mli
# ocamlc -w +A-30-42-41-48-40-4 -safe-string -strict-sequence -bin-annot -g -c landmark_graph.ml
# ocamlc -w +A-30-42-41-48-40-4 -safe-string -strict-sequence -bin-annot -g -c landmark.mli
# ocamlc -w +A-30-42-41-48-40-4 -safe-string -strict-sequence -bin-annot -g -c landmark.ml
# ocamlmklib -o landmarks clock.o landmark_misc.cmo landmark_graph.cmo landmark.cmo
### stderr ###
# /usr/bin/ld: clock.o: relocation R_X86_64_PC32 against undefined symbol `caml_copy_int64' can not be used when making a shared object; recompile with -fPIC
# /usr/bin/ld: final link failed: Bad value
# collect2: error: ld returned 1 exit status
# make[1]: *** [Makefile:38: landmarks.cma] Error 2
# make: *** [Makefile:15: landmarks] Error 2

I don't have the time right now to try adding the suggested flag, but I'll do later. In any case, I wanted to notify about it.

I am using a Fedora 26-alpha with GCC 7, so it is not impossible that it is an issue due to this somewhat bleeding-edge configuration. However, I am able to compile landmarks on OCaml 4.04.0 without issues.

use qualified names

I just tried landmarks on an experimental program of mine, and it is really nice: the web interface is convenient and it's really easy to set up, so thanks!

A small change that might be nice, when using [@@@landmarks auto], would be to have function names qualified by their path from the name of the current module. For instance, in:

(* file A.ml *)

let x =module B = struct
  let y =end

we would have, in the trace, "A.x" and "A.B.y" instead of "x" and "y".

Concurrency support (eg. Async, Lwt).

Landmarks expects landmarks to be correctly nested, making it impossible to bench interleaved asynchronous code, for instance when using Lwt or Async.

(* This will not measure the request, only the creation of the monad which takes 0ms *)
let[@landmark] test () =
  let* response = http_request () in
  process_response response

(* This will probably crash in non toy examples as other threads will probably interleave incompatible enter/leaves *)
let[@landmark] test () =
  Landmarks.enter mark;
  let* response = http_request () in
  let res = process_response response in
  Landmarks.leave mark;
  res

Is it something we'd want to support in landmarks ? Has it already been looked into ?

I'm keen to try implementing it, but at the same time it seems to me it would require some convoluted way to register threads with landmarks and a bunch of lookup maps that may impact the program performances, which is never great for a bench tool.

Bugs with polymorphism contraints in let-bindings

We have problem with polymorphism constraints:

let[@landmark] f : type t. t -> t = fun x -> x`

will trigger a clean but disapointing error message:

ppx_landmark: this landmark annotation requires a name argument

whereas exlicititly providing the name

let[@landmark "f"] f : type t. t -> t = fun x -> x

generates an unlocated type error after the ppx:

File "none", line 1:
Error: This definition has type 't -> 't which is less general than
't0. 't0 -> 't0

Improve "output=temporary"

  • Show the generated filename on stderr, not stdout (which is more likely to be redirected).
  • Allow to specify a target directory (e.g. output=temporary:profile would create files under profile/ in the current directory).
  • In the generated file, dump Sys.argv.

A example of typechecking failure in generated code

How to reproduce:

Run the ppx in automatic mode on :

  module M = struct
    type t = A | B
  end

  class a =
    object
      method f = M.A
    end

  class b =
    object
      inherit a

      method! f = B
    end

What happens ?

Class b get translated into:

    class b =
      object
        inherit  a
        method! f =
          Landmark.enter __generated_landmark;
          let r = try B with e -> (Landmark.exit  __generated_landmark; Landmark.raise e) in
          Landmark.exit  __generated_landmark;
          r
   end

which triggers "Error: Unbound constructor B" because the typechecker does not know at this point that the B has the same type as the method's returned type.

Proposed Solution

We could add type annotations ("'a") to guide the inference (we have to make sure that 'a is fresh of course).

    class b =
      object
        inherit  a
        method! f : 'a =
          Landmark.enter __generated_landmark;
          let r = try (B : 'a) with e -> (Landmark.exit  __generated_landmark; Landmark.raise e) in
          Landmark.exit  __generated_landmark;
          r
   end

If there's an annotation on the returned type we probably should copy it around the body in a similar fashion.

Let-in-annotation may introduce "unused variables"

For instance:

let () = 
  let[@landmark] rec f () = ()
  and g () = f () in 
  g ()

will produce unused variable "f".
This is a bug in the ppx "translation".

On the other hand, the warning will disappear with:

let f = ()
let () = 
  let[@landmark] rec f () = ()
  and g () = f () in 
  g ()

this seems to be a bug in the ocaml compiler.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.