{"id":13508424,"url":"https://github.com/shinyscorpion/wobserver","last_synced_at":"2025-05-15T08:10:50.360Z","repository":{"id":57556704,"uuid":"81105471","full_name":"shinyscorpion/wobserver","owner":"shinyscorpion","description":"Web based metrics, monitoring, and observer","archived":false,"fork":false,"pushed_at":"2020-06-06T02:35:22.000Z","size":293,"stargazers_count":929,"open_issues_count":9,"forks_count":83,"subscribers_count":31,"default_branch":"master","last_synced_at":"2025-04-30T00:30:42.286Z","etag":null,"topics":["elixir","erlang","hex","metrics","monitoring"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shinyscorpion.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-02-06T16:11:10.000Z","updated_at":"2025-04-17T06:03:54.000Z","dependencies_parsed_at":"2022-09-14T12:30:34.454Z","dependency_job_id":null,"html_url":"https://github.com/shinyscorpion/wobserver","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shinyscorpion%2Fwobserver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shinyscorpion%2Fwobserver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shinyscorpion%2Fwobserver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shinyscorpion%2Fwobserver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shinyscorpion","download_url":"https://codeload.github.com/shinyscorpion/wobserver/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252417798,"owners_count":21744682,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","erlang","hex","metrics","monitoring"],"created_at":"2024-08-01T02:00:52.867Z","updated_at":"2025-05-15T08:10:50.217Z","avatar_url":"https://github.com/shinyscorpion.png","language":"Elixir","funding_links":[],"categories":["Instrumenting / Monitoring"],"sub_categories":[],"readme":"# Wobserver\n\n[![Hex.pm](https://img.shields.io/hexpm/v/wobserver.svg \"Hex\")](https://hex.pm/packages/wobserver)\n[![Build Status](https://travis-ci.org/shinyscorpion/wobserver.svg?branch=master)](https://travis-ci.org/shinyscorpion/wobserver)\n[![Coverage Status](https://coveralls.io/repos/github/shinyscorpion/wobserver/badge.svg?branch=master)](https://coveralls.io/github/shinyscorpion/wobserver?branch=master)\n[![Inline docs](http://inch-ci.org/github/shinyscorpion/wobserver.svg?branch=master)](http://inch-ci.org/github/shinyscorpion/wobserver)\n[![Deps Status](https://beta.hexfaktor.org/badge/all/github/shinyscorpion/wobserver.svg)](https://beta.hexfaktor.org/github/shinyscorpion/wobserver)\n[![Hex.pm](https://img.shields.io/hexpm/l/wobserver.svg \"License\")](LICENSE)\n\nWeb based metrics, monitoring, and observer.\n\nWe are talking about `:wobserver` at [ElixirConf 2017](http://www.elixirconf.eu/#programme). Check out the [presentation and samples](https://github.com/IanLuites/wobserver-elixirconf-2017) and our other talk about [Task Bunny](https://github.com/shinyscorpion/task_bunny).\n\n\u003ca href=\"http://imgur.com/a/44TR5\" target=\"_blank\"\u003e\u003cimg src=\"http://i.imgur.com/9rybvU2.gif\" alt=\"Click to see more images.\" width=\"210\" height=\"250\" /\u003e\u003c/a\u003e\n\n[_Click to view images_](http://imgur.com/a/44TR5)\n\n**Functionality:**\n\n* Drop-in monitoring though web interface.\n* Metrics endpoint (`/metrics`) for system monitoring.\n  (Default: [Prometheus](https://prometheus.io/))\n* Monitoring automation through JSON API.\n* Node management and discovery behind firewalls and load balancers.\n* Easy to extend:\n    * Add custom metrics and pages for your project, just by adding them in the config.\n    * Just 3 lines of code to add pages/metrics for your library, when users have `:wobserver` installed.\n      (See [how](#library-integration).)\n\n## Table of contents\n\n* [wobserver](#wobserver)\n* [Table of contents](#table-of-contents)\n* [Installation](#installation)\n    * [Hex](#hex)\n    * [Build manually](#build-manually)\n* [Usage](#usage)\n    * [Browser](#browser)\n    * [API](#api)\n        * [Remote nodes](#remote-nodes)\n        * [System](#system)\n        * [Allocators](#allocators)\n        * [Application](#application)\n        * [Process](#process)\n        * [Ports](#ports)\n        * [Table view](#table-view)\n    * [Metrics](#usage-metrics)\n* [Configuration](#configuration)\n    * [Port](#port)\n    * [Mode](#mode)\n        * [Standalone](#mode-standalone)\n        * [Plug](#mode-plug)\n    * [Node Discovery](#node-discovery)\n    * [Metrics](#configure-metrics)\n        * [Add Metrics](#add-metrics)\n        * [Formatting](#formatting-metrics)\n    * [Pages](#pages)\n        * [Config](#pages-app)\n        * [Dynamically](#pages-dynamic)\n* [Library Integration](#library-integration)\n* [Improvements](#improvements)\n* [Contributors](#contributors)\n* [license](#License)\n\n\n## Installation\n\n### Hex\n\nAdd Wobserver as a dependency to your `mix.exs` file:\n\n```elixir\ndef deps do\n  [{:wobserver, \"~\u003e 0.1\"}]\nend\n```\n\nand add it to your list of applications:\n\n```elixir\ndef application do\n  [applications: [:wobserver]]\nend\n```\n\nThen run `mix deps.get` in your shell to fetch the dependencies.\n\n__Note:__ Check out [plug mode](#mode-plug) to integrate with a *Phoenix* or other web application. (Prevents startup of separate web server.)\n\n### Build manually\n\nRun the following commands to build the project:\n```bash\n$ npm install\n$ mix deps.get\n$ mix build\n```\n\n**Note:** Use the package generated by `mix build` if you want to include the local wobserver in your application.\n(Unpack in your deps.)\n\n### Github\nWobserver does not support being included directly from github.\nThe required assets are not included in the repo in build form and can therefore not be used.\nIt is possible to build locally and use the generated package.\n(See [Build manually](#build-manually) for more information.)\n\n## Usage\n\n### Browser\nTo view the web interface just enter `http://\u003chost\u003e[:\u003cport\u003e]/` in the browser and it should show the `:wobserver` interface.\nThe default port is 4001, but the port can be changed in the configuration.\n\nA sample interface can be viewed [here](http://imgur.com/a/IqO6O).\n\n### API\n\nThe API can be accessed by calling `http://\u003chost\u003e[:\u003cport\u003e]/api/`.\nThe index will return `404`, but specific endpoints should return results.\n\n#### \u003ca name=\"remote-nodes\"\u003e\u003c/a\u003e Remote nodes\n\nThe API provides a list of remote nodes by calling `http://\u003chost\u003e[:\u003cport\u003e]/api/nodes`.\n\nThe API of remote nodes can be accessed by calling the API endpoint and prefixing the node name, host, or host:port.\n\nFor example considering the following node list:\n```json\n[\n  {\n    \"port\": 4001,\n    \"name\": \"node_prime\",\n    \"local?\": true,\n    \"host\": \"192.168.5.55\"\n  },\n  {\n    \"port\": 80,\n    \"name\": \"remote\",\n    \"local?\": false,\n    \"host\": \"80.23.1.165\"\n  }\n]\n```\n\nThe following calls would all work for the first node:\n*(`local` is a reserved name that always points to the local node.*)\n```bash\nhttp://\u003chost\u003e[:\u003cport\u003e]/api/local/system\nhttp://\u003chost\u003e[:\u003cport\u003e]/api/node_prime/system\nhttp://\u003chost\u003e[:\u003cport\u003e]/api/192.168.5.55/system\nhttp://\u003chost\u003e[:\u003cport\u003e]/api/192.168.5.55:4001/system\n```\n\nAnd these calls would work for the second node:\n```bash\nhttp://\u003chost\u003e[:\u003cport\u003e]/api/remote/system\nhttp://\u003chost\u003e[:\u003cport\u003e]/api/80.23.1.165/system\nhttp://\u003chost\u003e[:\u003cport\u003e]/api/80.23.1.165:80/system\n```\n\n#### \u003ca name=\"system\"\u003e\u003c/a\u003e System\nThe API provides a list of system information by calling `http://\u003chost\u003e[:\u003cport\u003e]/api/system`.\n\nThe `scheduler` is a list of load values (0-1) for each scheduler.\n\nExample:\n```json\n{\n  \"statistics\": {\n    \"uptime\": 459876,\n    \"process_total\": 122,\n    \"process_running\": 0,\n    \"process_max\": 262144,\n    \"output\": 1259201,\n    \"input\": 12945380\n  },\n  \"scheduler\": [\n    0.0037370416873916392,\n    0.0003088661849770247,\n    0.0003072993680801981,\n    0.00030274231847091137,\n    0.0004706952361156354,\n    0.00028556537348788645,\n    0.00025471141618606366,\n    0.0002522242536713918\n  ],\n  \"memory\": {\n    \"total\": 30275576,\n    \"process\": 5242800,\n    \"ets\": 886544,\n    \"code\": 13635797,\n    \"binary\": 288744,\n    \"atom\": 594561\n  },\n  \"cpu\": {\n    \"schedulers_online\": 8,\n    \"schedulers_available\": 8,\n    \"schedulers\": 8,\n    \"logical_processors_online\": 8,\n    \"logical_processors_available\": \"unknown\",\n    \"logical_processors\": 8\n  },\n  \"architecture\": {\n    \"wordsize_internal\": 8,\n    \"wordsize_external\": 8,\n    \"threads\": true,\n    \"thread_pool_size\": 10,\n    \"system_architecture\": \"x86_64-apple-darwin15.6.0\",\n    \"smp_support\": true,\n    \"otp_release\": \"19\",\n    \"kernel_poll\": false,\n    \"erts_version\": \"8.2\",\n    \"elixir_version\": \"1.4.0\"\n  }\n}\n```\n\n#### \u003ca name=\"allocators\"\u003e\u003c/a\u003e Allocators\nThe API provides a list of allocators and their size by calling `http://\u003chost\u003e[:\u003cport\u003e]/api/allocators`.\n\nExample:\n```json\n[\n  {\n    \"type\": \"sl_alloc\",\n    \"carrier\": 294912,\n    \"block\": 664\n  },\n  {\n    \"type\": \"std_alloc\",\n    \"carrier\": 1081344,\n    \"block\": 498184\n  },\n  {\n    \"type\": \"ll_alloc\",\n    \"carrier\": 35913728,\n    \"block\": 26080144\n  },\n  {\n    \"type\": \"eheap_alloc\",\n    \"carrier\": 9830400,\n    \"block\": 2634720\n  },\n  {\n    \"type\": \"ets_alloc\",\n    \"carrier\": 3178496,\n    \"block\": 890880\n  },\n  ...\n]\n```\n\n#### \u003ca name=\"application\"\u003e\u003c/a\u003e Application\nThe API provides a list of applications and their descriptions by calling `http://\u003chost\u003e[:\u003cport\u003e]/api/application`.\n\nThe information for a specific application, including the process hierarchy can be found by calling `http://\u003chost\u003e[:\u003cport\u003e]/api/application/\u003capplication-name\u003e`.\n\nExample:\n`http://localhost:4001/api/application`\n```json\n[\n  {\n    \"version\": \"0.1.0\",\n    \"name\": \"wobserver\",\n    \"description\": \"Web based metrics, monitoring, and observer.\"\n  },\n  {\n    \"version\": \"1.3.0\",\n    \"name\": \"plug\",\n    \"description\": \"A specification and conveniences for composable modules between web applications\"\n  },\n  {\n    \"version\": \"1.1.0\",\n    \"name\": \"cowboy\",\n    \"description\": \"Small, fast, modular HTTP server.\"\n  },\n  {\n    \"version\": \"1.2.1\",\n    \"name\": \"ranch\",\n    \"description\": \"Socket acceptor pool for TCP protocols.\"\n  },\n  {\n    \"version\": \"0.6.1\",\n    \"name\": \"credo\",\n    \"description\": \"A static code analysis tool for the Elixir language with a focus on code consistency and teaching.\"\n  },\n  {\n    \"version\": \"0.2.0\",\n    \"name\": \"bunt\",\n    \"description\": \"256 color ANSI coloring in the terminal\"\n  },\n  {\n    \"version\": \"1.6.5\",\n    \"name\": \"hackney\",\n    \"description\": \"simple HTTP client\"\n  },\n  {\n    \"version\": \"1.4.0\",\n    \"name\": \"logger\",\n    \"description\": \"logger\"\n  },\n  ...\n]\n```\n`http://localhost:4001/api/application/elixir`\n```json\n{\n  \"pid\": \"#PID\u003c0.59.0\u003e\",\n  \"name\": \"\u003c0.59.0\u003e\",\n  \"meta\": {\n    \"status\": \"waiting\",\n    \"init\": \"proc_lib.init_p/5\",\n    \"current\": \"application_master.main_loop/2\",\n    \"class\": \"application\"\n  },\n  \"children\": [\n    {\n      \"pid\": \"#PID\u003c0.60.0\u003e\",\n      \"name\": \"\u003c0.60.0\u003e\",\n      \"meta\": {\n        \"status\": \"waiting\",\n        \"init\": \"application_master.start_it/4\",\n        \"current\": \"application_master.loop_it/4\",\n        \"class\": \"unknown\"\n      },\n      \"children\": [\n          {\n            \"pid\": \"#PID\u003c0.61.0\u003e\",\n            \"name\": \"elixir_sup\",\n            \"meta\": {\n              \"status\": \"waiting\",\n              \"init\": \"proc_lib.init_p/5\",\n              \"current\": \"gen_server.loop/6\",\n              \"class\": \"supervisor\"\n            },\n            \"children\": [\n              {\n              \"pid\": \"#PID\u003c0.62.0\u003e\",\n              \"name\": \"elixir_config\",\n              \"meta\": {\n                \"status\": \"waiting\",\n                \"init\": \"proc_lib.init_p/5\",\n                \"current\": \"gen_server.loop/6\",\n                \"class\": \"gen_server\"\n              },\n              \"children\": []\n            },\n            {\n              \"pid\": \"#PID\u003c0.63.0\u003e\",\n              \"name\": \"elixir_code_server\",\n              \"meta\": {\n                \"status\": \"waiting\",\n                \"init\": \"proc_lib.init_p/5\",\n                \"current\": \"gen_server.loop/6\",\n                \"class\": \"gen_server\"\n              },\n              \"children\": []\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}\n```\n\n#### \u003ca name=\"process\"\u003e\u003c/a\u003e Process\nThe API provides a list of processes and their basic information by calling `http://\u003chost\u003e[:\u003cport\u003e]/api/process`.\n\nThe information for a specific process, including a links, memory usage, and state can be found by calling `http://\u003chost\u003e[:\u003cport\u003e]/api/application/\u003cprocess-name\u003e`.\n\nThe process name can be given as pid, name, or short pid.\n\nSo all the following are valid:\n```bash\nhttp://localhost:4001/api/process/\u003c0.247.0\u003e\nhttp://localhost:4001/api/process/#PID\u003c0.247.0\u003e   # Rememeber to url encode # -\u003e %23\nhttp://localhost:4001/api/process/Wobserver.Supervisor\n```\n\nExample:\n`http://localhost:4001/api/process`\n```json\n{\n  \"processes\": [\n    {\n      \"reductions\": 162714,\n      \"pid\": \"#PID\u003c0.247.0\u003e\",\n      \"nr1\": \"0\",\n      \"message_queue_length\": 0,\n      \"memory\": 11888,\n      \"init\": \"timer_server\",\n      \"current\": \"gen_server.loop/6\"\n    },\n    {\n      \"reductions\": 95,\n      \"pid\": \"#PID\u003c0.243.0\u003e\",\n      \"nr1\": \"0\",\n      \"message_queue_length\": 0,\n      \"memory\": 2792,\n      \"init\": \"erlang.apply/2\",\n      \"current\": \"io.execute_request/2\"\n    },\n    {\n      \"reductions\": 954,\n      \"pid\": \"#PID\u003c0.242.0\u003e\",\n      \"nr1\": \"0\",\n      \"message_queue_length\": 0,\n      \"memory\": 16808,\n      \"init\": \"Elixir.IEx.Evaluator.init/4\",\n      \"current\": \"Elixir.IEx.Evaluator.loop/3\"\n    },\n    ...\n  ]\n}\n```\n`http://localhost:4001/api/process/\u003c0.247.0\u003e`\n```json\n{\n  \"trap_exit\": true,\n  \"state\": \"[]\",\n  \"relations\": {\n    \"links\": [\n      \"#PID\u003c0.53.0\u003e\"\n    ],\n    \"group_leader\": \"#PID\u003c0.33.0\u003e\",\n    \"ancestors\": [\n      \"kernel_safe_sup\",\n      \"kernel_sup\",\n      \"#PID\u003c0.34.0\u003e\"\n    ]\n  },\n  \"registered_name\": \"timer_server\",\n  \"priority\": \"normal\",\n  \"pid\": \"#PID\u003c0.247.0\u003e\",\n  \"meta\": {\n    \"status\": \"waiting\",\n    \"init\": \"proc_lib.init_p/5\",\n    \"current\": \"gen_server.loop/6\",\n    \"class\": \"gen_server\"\n  },\n  \"message_queue_len\": 0,\n  \"memory\": {\n    \"total\": 0,\n    \"stack_size\": 9,\n    \"stack_and_heap\": 1974,\n    \"heap_size\": 1598,\n    \"gc_min_heap_size\": 233,\n    \"gc_full_sweep_after\": 65535\n  },\n  \"error_handler\": \"error_handler\"\n}\n```\n\n#### \u003ca name=\"ports\"\u003e\u003c/a\u003e Ports\nThe API provides a list of ports and their owners by calling `http://\u003chost\u003e[:\u003cport\u003e]/api/ports`.\n\nExample:\n`http://localhost:4001/api/ports`\n```json\n[\n  {\n    \"output\": 0,\n    \"os_pid\": \"undefined\",\n    \"name\": \"forker\",\n    \"links\": [],\n    \"input\": 0,\n    \"id\": 0,\n    \"connected\": \"#PID\u003c0.0.0\u003e\"\n  },\n  {\n    \"output\": 3,\n    \"os_pid\": \"undefined\",\n    \"name\": \"efile\",\n    \"links\": [\n      \"#PID\u003c0.4.0\u003e\"\n    ],\n    \"input\": 46,\n    \"id\": 8,\n    \"connected\": \"#PID\u003c0.4.0\u003e\"\n  },\n  {\n    \"output\": 18810,\n    \"os_pid\": \"undefined\",\n    \"name\": \"efile\",\n    \"links\": [\n      \"#PID\u003c0.44.0\u003e\"\n    ],\n    \"input\": 23874,\n    \"id\": 4680,\n    \"connected\": \"#PID\u003c0.44.0\u003e\"\n  },\n  ...\n]\n```\n\n#### \u003ca name=\"table-view\"\u003e\u003c/a\u003e Table view\nThe API provides a list of tables and their details by calling `http://\u003chost\u003e[:\u003cport\u003e]/api/table`.\n\nThe information for a specific details, including a the actual data can be found by calling `http://\u003chost\u003e[:\u003cport\u003e]/api/table/\u003ctable-name\u003e`.\n\nExample:\n`http://localhost:4001/api/table`\nExample:\n```json\n[\n  {\n    \"type\": \"set\",\n    \"size\": 0,\n    \"protection\": \"protected\",\n    \"owner\": \"#PID\u003c0.247.0\u003e\",\n    \"name\": \"timer_interval_tab\",\n    \"meta\": {\n      \"write_concurrency\": false,\n      \"read_concurrency\": false,\n      \"compressed\": false\n    },\n    \"memory\": 304,\n    \"id\": \"timer_interval_tab\"\n  },\n  {\n    \"type\": \"ordered_set\",\n    \"size\": 7,\n    \"protection\": \"protected\",\n    \"owner\": \"#PID\u003c0.247.0\u003e\",\n    \"name\": \"timer_tab\",\n    \"meta\": {\n      \"write_concurrency\": false,\n      \"read_concurrency\": false,\n      \"compressed\": false\n    },\n    \"memory\": 304,\n    \"id\": \"timer_tab\"\n  },\n  {\n    \"type\": \"set\",\n    \"size\": 7,\n    \"protection\": \"public\",\n    \"owner\": \"#PID\u003c0.228.0\u003e\",\n    \"name\": \"workstore\",\n    \"meta\": {\n      \"write_concurrency\": false,\n      \"read_concurrency\": false,\n      \"compressed\": false\n    },\n    \"memory\": 5138,\n    \"id\": 417840\n  },\n  ...\n]\n```\n`http://localhost:4001/api/table/timer_interval_tab`\n```json\n{\n  \"type\": \"set\",\n  \"size\": 0,\n  \"protection\": \"protected\",\n  \"owner\": \"#PID\u003c0.247.0\u003e\",\n  \"name\": \"timer_interval_tab\",\n  \"meta\": {\n    \"write_concurrency\": false,\n    \"read_concurrency\": false,\n    \"compressed\": false\n  },\n  \"memory\": 304,\n  \"id\": \"timer_interval_tab\",\n  \"data\": []\n}\n\n```\n\n### Metrics\nMetrics are available by calling `http://\u003chost\u003e[:\u003cport\u003e]/metrics`.\n\nThe metrics are by default formatted for [Prometheus](https://prometheus.io/), but can be configured to work with any system.\nAn explanation of how to configure the metrics format and how to add metrics to the output will be added later.\n\n`http://localhost:4001/metrics`\n```bash\n# HELP erlang_vm_used_memory_bytes Memory usage of the Erlang VM.\n# TYPE erlang_vm_used_memory_bytes gauge\nerlang_vm_used_memory_bytes{node=\"10.74.181.35\",type=\"atom\"} 553593\nerlang_vm_used_memory_bytes{node=\"10.74.181.35\",type=\"binary\"} 359552\nerlang_vm_used_memory_bytes{node=\"10.74.181.35\",type=\"code\"} 13533686\nerlang_vm_used_memory_bytes{node=\"10.74.181.35\",type=\"ets\"} 1899472\nerlang_vm_used_memory_bytes{node=\"10.74.181.35\",type=\"process\"} 6048552\n# HELP erlang_vm_used_io_bytes IO counter for the Erlang VM.\n# TYPE erlang_vm_used_io_bytes counter\nerlang_vm_used_io_bytes{node=\"10.74.181.35\",type=\"input\"} 11301316\nerlang_vm_used_io_bytes{node=\"10.74.181.35\",type=\"output\"} 618157\n```\n\n## Configuration\n### Port\nThe port can be set in the config by setting `:port` for `:wobserver` to a valid number.\n\n#### Example config\n```elixir\nconfig :wobserver,\n  port: 80\n```\n\n### Mode\nWobserver runs by default in `:standalone` mode.\nThis means that `:wobserver` will start its own `:cowboy` listeners on a separate port.\nStandalone mode is ideal for drop-in web viewing, but might not be ideal if another part of the application is already running an web server.\nIt is possible to enable `:plug` mode to prevent `:wobserver` from starting `:cowboy` to handle web requests.\n\n#### \u003ca name=\"mode-standalone\"\u003e\u003c/a\u003e Standalone\nStandalone mode is the default operating mode.\nA `:cowboy` (ranch) listener will be started with 10 accepters and a websocket.\nSet `mode` to `:standalone` in the `:wobserver` configuration to force standalone mode.\n\nExample:\n```elixir\nconfig :wobserver,\n  mode: :standalone\n```\n\n#### \u003ca name=\"mode-plug\"\u003e\u003c/a\u003e Plug\nPlug mode prevents `:wobserver` from starting `:cowboy` (ranch).\nSet `mode` to `:plug` in the `:wobserver` configuration to use plug mode.\nSet `remote_url_prefix` to the url prefix you put `:wobserver` behind to make sure dns node discovery still functions.\n\n\n##### cowboy\n\nPlug mode prevents `:wobserver` from starting `:cowboy` (ranch). Set `mode` to `:plug` in the `:wobserver`\nconfiguration to use plug mode. Set `remote_url_prefix` to the url prefix you put `:wobserver` behind to make\nsure dns node discovery still functions.\n\nAdd the following line of code to the application's router to forward requests to `:wobserver`:\n\n```elixir\n  forward \"/wobserver\", to: Wobserver.Web.Router\n```\n\nAdd the following option to the `:cowboy` child_spec to enable use of the `:wobserver` websocket:\n\n```elixir\ndispatch: [\n    {:_, [\n      {\"/ws\", Wobserver.Web.Client, []},\n      {:_, Cowboy.Handler, {\u003cyour own router\u003e, []}}\n    ]}\n  ],\n```\n\n##### Phoenix\n\nAdd the following line of code to the Phoenix router to forward requests to `:wobserver`:\n\n```elixir\n  forward \"/wobserver\", Wobserver.Web.Router\n```\n\nAdd the following option to your Phoenix applications Endpoint to enable use of the `:wobserver` websocket (the\npath should match what is in the 'forward' in your router):\n\n```elixir\n  socket \"/wobserver\", Wobserver.Web.PhoenixSocket\n```\n\n##### Cowboy Example\n\n__config.exs__\n```elixir\nconfig :wobserver,\n  mode: :plug,\n  remote_url_prefix: \"/wobserver\"\n```\n__my_router.ex__\n```elixir\ndefmodule MyApp.MyRouter do\n  use Plug.Router\n\n  plug :match\n  plug :dispatch\n\n  forward \"/wobserver\", to: Wobserver.Web.Router\nend\n```\n__application.ex__\n```elixir\ndefmodule MyApp.Application do\n  use Application\n\n  alias Plug.Adapters.Cowboy\n\n  def start(_type, _args) do\n    import Supervisor.Spec, warn: false\n\n    options = [\n      dispatch: [\n        {:_, [\n          {\"/wobserver/ws\", Wobserver.Web.Client, []},\n          {:_, Cowboy.Handler, {MyApp.MyRouter, []}}\n        ]}\n      ],\n    ]\n\n    children = [\n      Cowboy.child_spec(:http, MyApp.MyRouter, [], options)\n    ]\n\n    opts = [strategy: :one_for_one, name: MyApp.Supervisor]\n    Supervisor.start_link(children, opts)\n  end\nend\n```\n\n\n### Node Discovery\nThe method used can be set in the config file by setting:\n```elixir\nconfig :wobserver,\n  discovery: :none\n```\n\nThe following methods can be used: (default: `:none`)\n\n  - `:none`, just returns the local node.\n  - `:dns`, use DNS to search for other nodes.\n    The option `discovery_search` needs to be set to filter entries.\n  - `:custom`, a function as String.\n\n#### Example config\nNo discovery: *(default)*\n```elixir\nconfig :wobserver,\n  port: 80,\n  discovery: :none\n```\n\nUsing dns as discovery service:\n```elixir\nconfig :wobserver,\n  port: 80,\n  discovery: :dns,\n  discovery_search: \"google.nl\"\n```\n\nUsing a custom function:\n```elixir\nconfig :wobserver,\n  port: 80,\n  discovery: :custom,\n  discovery_search: \u0026MyApp.CustomDiscovery.discover/0\n```\n\nUsing an anonymous function:\n```elixir\nconfig :wobserver,\n  port: 80,\n  discovery: :custom,\n  discovery_search: fn -\u003e [] end\n```\n\nBoth the custom and anonymous functions can be given as a String, which will get evaluated.\n\n### \u003ca name=\"configure-metrics\"\u003e\u003c/a\u003e Metrics\n#### \u003ca name=\"add-metrics\"\u003e\u003c/a\u003e Add Metrics\n#### Config\nMetrics and metric generators can be added by setting them in the configuration.\n\nTo add custom metrics set the `:metrics` option.\nThe `:metrics` option must be a keyword list with the following keys:\n  * `additional`, for a keyword list with additional metrics.\n  * `generators`, for a list of metric generators.\n\nThe following settings are accepted for `additional`:\n  - `keyword` list, the key is the name of the metric and the value is the metric data.\n\nThe following inputs are accepted for metric generators:\n  - `list` of callable functions.\n    Every function should return a keyword list with as key the name of the metric and as value the metric data.\n\nFor more information about how to format metric data see: [`Wobserver.Util.Metrics.Formatter.format_all/1`](https://hexdocs.pm/wobserver/Wobserver.Util.Metrics.Formatter.html#format_all/1).\n\nFor example this configuration:\n```elixir\nconfig :wobserver,\n  metrics: [\n    additional: [\n      example: {fn -\u003e [red: 5] end, :gauge, \"Description\"},\n    ],\n    generators: [\n      \"\u0026MyApp.generator/0\",\n      fn -\u003e [bottles: {fn -\u003e [wall: 8, floor: 10] end, :gauge, \"Description\"}] end\n      fn -\u003e [server: {\"MyApp.Server.metrics/0\", :gauge, \"Description\"}] end\n    ]\n  ]\n```\n\n#### Dynamically\nMetrics and metric generators can also be added dynamically at runtime.\n\nTo register a metric you need to pass a keyword list to `Wobserver.register` with the same data as you would set in the configuration file.\n\nFor example:\n```elixir\nWobserver.register :metric, [example: {fn -\u003e [red: 5] end, :gauge, \"Description\"}]\n```\n\nTo register a metric generator you need to pass a list of functions to `Wobserver.register`.\n\nFor example:\n```elixir\nWobserver.register :metric, [\u0026MyLibrary.Metrics.generate/0]\n```\n\n#### \u003ca name=\"formatting-metrics\"\u003e\u003c/a\u003e Formatting\nA custom formatter can be created for output of metrics by implementing the `Wobserver.Util.Metrics.Formatter` behavior.\nThis custom formatter can be enabled in the configuration file by setting `metric_format`.\n\nFor example this configuration:\n```elixir\nconfig :wobserver,\n  metric_format: JsonFormatter\n```\n\nAnd this simple JSON formatter:\n```elixir\ndefmodule SimpleJsonFormatter do\n  @behaviour Wobserver.Util.Metrics.Formatter\n\n  def format_data(name, data, type, help) do\n    formatted_data =\n      data\n      |\u003e Enum.map(fn {value, labels} -\u003e\n           %{value: value, labels: Enum.into(labels, %{})}\n         end)\n\n    %{\n      name: name,\n      type: type,\n      description: help,\n      data: formatted_data\n    }\n    |\u003e Poison.encode!\n  end\n\n  def combine_metrics(metrics) do\n    \"[\" \u003c\u003e Enum.join(metrics,\",\") \u003c\u003e \"]\"\n  end\n\n  def merge_metrics(metrics) do\n    \"[\" \u003c\u003e Enum.join(metrics,\",\") \u003c\u003e \"]\"\n  end\nend\n```\n\nProduce the following output:\n```json\n[\n  [\n    {\n      \"type\": \"gauge\",\n      \"name\": \"erlang_vm_used_memory_bytes\",\n      \"description\": \"Memory usage of the Erlang VM.\",\n      \"data\": [\n        {\n          \"value\": 654241,\n          \"labels\": {\n            \"type\": \"atom\",\n            \"node\": \"192.168.1.88\"\n          }\n        },\n          {\n          \"value\": 503464,\n          \"labels\": {\n            \"type\": \"binary\",\n            \"node\": \"192.168.1.88\"\n          }\n        },\n        {\n          \"value\": 14459399,\n          \"labels\": {\n            \"type\": \"code\",\n            \"node\": \"192.168.1.88\"\n          }\n        },\n        {\n          \"value\": 2073072,\n          \"labels\": {\n            \"type\": \"ets\",\n            \"node\": \"192.168.1.88\"\n          }\n        },\n        {\n          \"value\": 6008488,\n          \"labels\": {\n            \"type\": \"process\",\n            \"node\": \"192.168.1.88\"\n          }\n        }\n      ]\n    },\n    {\n      \"type\": \"counter\",\n      \"name\": \"erlang_vm_used_io_bytes\",\n      \"description\": \"IO counter for the Erlang VM.\",\n      \"data\": [\n        {\n          \"value\": 29523254,\n          \"labels\": {\n            \"type\": \"input\",\n            \"node\": \"192.168.1.88\"\n          }\n        },\n        {\n          \"value\": 9960593,\n          \"labels\": {\n            \"type\": \"output\",\n            \"node\": \"192.168.1.88\"\n          }\n        }\n      ]\n    }\n  ]\n]\n```\n\n### \u003ca name=\"pages\"\u003e\u003c/a\u003e Pages\nPages are custom views in the web interface and endpoints in the JSON API for an application or library.\n\nThere are two ways to add a custom page:\n* *config*, set a list of custom pages in the mix config.\n* *registration*, call `Wobserver.register/2` and dynamically add pages.\n\n#### \u003ca name=\"pages-config\"\u003e\u003c/a\u003e Config\nAdding more pages to `:wobserver` can be done by setting the `:pages` option.\n\nThe `:pages` option must be a list of page data.\n\nThe page data can be formatted as:\n  * `{title, command, callback}`\n  * `{title, command, callback, options}`\n  * a `map` with the following fields:\n      * `title`\n      * `command`\n      * `callback`\n      * `options` (optional)\n\nFor more information and types see: [`Wobserver.Page.register/1`](https://hexdocs.pm/wobserver/Wobserver.Page.html#register/1).\n\nExample:\n```elixir\nconfig :wobserver,\n  pages: [\n    {\"Example\", :example, fn -\u003e %{x:  9} end}\n  ]\n```\n#### \u003ca name=\"pages-dynamic\"\u003e\u003c/a\u003e Dynamically\nDynamically register a page with `:wobserver` by calling [`Wobserver.register/2`](https://hexdocs.pm/wobserver/Wobserver.html#register/2).\n\nThe following inputs are accepted:\n  * `{title, command, callback}`\n  * `{title, command, callback, options}`\n  * a `map` with the following fields:\n      * `title`\n      * `command`\n      * `callback`\n      * `options` (optional)\n\nThe fields are used as followed:\n  * `title`, the name of the page. Is used for the web interface menu.\n  * `command`, single atom to associate the page with.\n  * `callback`, function to be evaluated, when the a api is called or page is viewd.\n                The result is converted to JSON and displayed.\n  * `options`, options for the page.\n\nThe following options can be set:\n  * `api_only` (`boolean`), if set to true the page won't show up in the web interface, but will only be available as API.\n  * `refresh` (`float`, 0-1), sets the refresh time factor. Used in the web interface to refresh the data on the page. Set to `0` for no refresh.\n\nExample:\n```elixir\nWobserver.register(:page, {\"My App\", :my_app, fn -\u003e %{data: 123} end})\n```\n\n## Library Integration\nIntegrating a library with `:wobserver` is done by calling [`Wobserver.register/2`](https://hexdocs.pm/wobserver/Wobserver.html#register/2), when the library loads, and dynamically adding pages and metrics.\n\n### Code\nTo safely integrate with `:wobserver` use the following code:\n```elixir\nif Code.ensure_loaded(Wobserver) == {:module, Wobserver} do\n  Wobserver.register :page, {\"My Library\", :my_library, fn -\u003e %{data: 123} end}\n  Wobserver.register :metric, [\u0026MyLibrary.Metrics.generate/0]\nend\n```\nThe above code will make sure that the library only calls register, when `:wobserver` is loaded.\nThis will prevent the library from trying to register, when `:wobserver` is not installed.\n\nFor an implementation see the `:task_bunny` library: [TaskBunny](https://github.com/shinyscorpion/task_bunny), [`lib/task_bunny.ex`](https://github.com/shinyscorpion/task_bunny/blob/master/lib/task_bunny.ex).\n\n### Remove Warnings\nThe above code will generate warnings while compiling the library.\n```bash\nwarning: function Wobserver.register/2 is undefined (module Wobserver is not available)\n```\n\nThere are two options to remove those warnings.\n\n#### Edit mix.exs\nThe `mix.exs` file can be edited to excluded `Wobserver` from reference checks.\n\nTo do this add the following line to `project/0` in your mix file:\n```elixir\n  xref: [exclude: [Wobserver]]\n```\n\n#### Kernel.apply\nThe code can be rewritten to use [`Kernel.apply/3`](https://hexdocs.pm/elixir/Kernel.html#apply/3).\nThe following code will be less readable and slightly slower, but will not generate warnings.\n```elixir\nif Code.ensure_loaded(Wobserver) == {:module, Wobserver} do\n  apply Wobserver, :register, [:page, {\"My Library\", :my_library, fn -\u003e %{data: 123} end}]\n  apply Wobserver, :register, [:metric, [\u0026MyLibrary.Metrics.generate/0]]\nend\n```\n\n## Improvements\n  - Cleanup namespaces.\n  - Cleanup readme, condense sample output.\n  - Overhaul web interface (make fancier/pleasant)\n\n## Contributors\n\n* [OvermindDL1](https://github.com/OvermindDL1) - Phoenix Socket support and lots of issue reports.\n\n\n## License\n\nWobserver source code is released under [the MIT License](LICENSE).\nCheck [LICENSE](LICENSE) file for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshinyscorpion%2Fwobserver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshinyscorpion%2Fwobserver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshinyscorpion%2Fwobserver/lists"}