github.com/netdata/go.d.plugin@v0.58.1/docs/how-to-write-a-module.md (about)

     1  <!--
     2  title: "How to write a Netdata collector in Go"
     3  description: "This guide will walk you through the technical implementation of writing a new Netdata collector in Golang, with tips on interfaces, structure, configuration files, and more."
     4  custom_edit_url: "https://github.com/netdata/go.d.plugin/edit/master/docs/how-to-write-a-module.md"
     5  sidebar_label: "How to write a Netdata collector in Go"
     6  learn_status: "Published"
     7  learn_topic_type: "Tasks"
     8  learn_rel_path: "Developers/External plugins/go.d.plugin"
     9  sidebar_position: 20
    10  -->
    11  
    12  # How to write a Netdata collector in Go
    13  
    14  ## Prerequisites
    15  
    16  - Take a look at our [contributing guidelines](https://github.com/netdata/.github/blob/main/CONTRIBUTING.md).
    17  - [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) this repository to your personal
    18    GitHub account.
    19  - [Clone](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository#:~:text=to%20GitHub%20Desktop-,On%20GitHub%2C%20navigate%20to%20the%20main%20page%20of%20the%20repository,Desktop%20to%20complete%20the%20clone.)
    20    locally the **forked** repository (e.g `git clone https://github.com/odyslam/go.d.plugin`).
    21  - Using a terminal, `cd` into the directory (e.g `cd go.d.plugin`)
    22  
    23  
    24  ## Write and test a simple collector
    25  
    26  > :exclamation: You can skip most of these steps if you first experiment directy with the existing 
    27  > [example module](https://github.com/netdata/go.d.plugin/tree/master/modules/example), which will 
    28  > give you an idea of  how things work.
    29  
    30  Let's assume you want to write a collector named `example2`.
    31  
    32  The steps are:
    33  
    34  - Add the source code to [`modules/example2/`](https://github.com/netdata/go.d.plugin/tree/master/modules).
    35      - [module interface](#module-interface).
    36      - [suggested module layout](#module-layout).
    37      - [helper packages](#helper-packages).
    38  - Add the configuration to [`config/go.d/example2.conf`](https://github.com/netdata/go.d.plugin/tree/master/config/go.d).
    39  - Add the module to [`config/go.d.conf`](https://github.com/netdata/go.d.plugin/blob/master/config/go.d.conf).
    40  - Import the module in [`modules/init.go`](https://github.com/netdata/go.d.plugin/blob/master/modules/init.go).
    41  - Update the [`available modules list`](https://github.com/netdata/go.d.plugin#available-modules).
    42  - To build it, run `make` from the plugin root dir. This will create a new `go.d.plugin` binary that includes your newly
    43    developed collector. It will be placed into the `bin` directory (e.g `go.d.plugin/bin`)
    44  - Run it in the debug mode `bin/godplugin -d -m <MODULE_NAME>`. This will output the `STDOUT` of the collector, the same
    45    output that is sent to the Netdata Agent and is transformed into charts. You can read more about this collector API in
    46    our [documentation](https://github.com/netdata/netdata/blob/master/src/collectors/plugins.d/README.md#external-plugins-api).
    47  - If you want to test the collector with the actual Netdata Agent, you need to replace the `go.d.plugin` binary that
    48    exists in the Netdata Agent installation directory with the one you just compiled. Once
    49    you [restart](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) the Netdata Agent, it will detect and run
    50    it, creating all the charts. It is advised not to remove the default `go.d.plugin` binary, but simply rename it
    51    to `go.d.plugin.old` so that the Agent doesn't run it, but you can easily rename it back once you are done.
    52  - Run `make clean` when you are done with testing.
    53  
    54  
    55  ## Module Interface
    56  
    57  Every module should implement the following interface:
    58  
    59  ```
    60  type Module interface {
    61      Init() bool
    62      Check() bool
    63      Charts() *Charts
    64      Collect() map[string]int64
    65      Cleanup()
    66  }
    67  ```
    68  
    69  ### Init method
    70  
    71  - `Init` does module initialization.
    72  - If it returns `false`, the job will be disabled.
    73  
    74  We propose to use the following template:
    75  
    76  ```
    77  // example.go
    78  
    79  func (e *Example) Init() bool {
    80      err := e.validateConfig()
    81      if err != nil {
    82          e.Errorf("config validation: %v", err)
    83          return false
    84      }
    85  
    86      someValue, err := e.initSomeValue()
    87      if err != nil {
    88          e.Errorf("someValue init: %v", err)
    89          return false
    90      }
    91      e.someValue = someValue
    92  
    93      // ...
    94      return true 
    95  }
    96  ```
    97  
    98  Move specific initialization methods into the `init.go` file. See [suggested module layout](#module-Layout).
    99  
   100  ### Check method
   101  
   102  - `Check` returns whether the job is able to collect metrics.
   103  - Called after `Init` and only if `Init` returned `true`.
   104  - If it returns `false`, the job will be disabled.
   105  
   106  The simplest way to implement `Check` is to see if we are getting any metrics from `Collect`. A lot of modules use such
   107  approach.
   108  
   109  ```
   110  // example.go
   111  
   112  func (e *Example) Check() bool {
   113      return len(e.Collect()) > 0
   114  }
   115  ```
   116  
   117  ### Charts method
   118  
   119  :exclamation: Netdata module produces [`charts`](https://github.com/netdata/netdata/blob/master/src/collectors/plugins.d/README.md#chart), not
   120  raw metrics.
   121  
   122  Use [`agent/module`](https://github.com/netdata/go.d.plugin/blob/master/agent/module/charts.go) package to create them,
   123  it contains charts and dimensions structs.
   124  
   125  - `Charts` returns the [charts](https://github.com/netdata/netdata/blob/master/src/collectors/plugins.d/README.md#chart) (`*module.Charts`).
   126  - Called after `Check` and only if `Check` returned `true`.
   127  - If it returns `nil`, the job will be disabled
   128  - :warning: Make sure not to share returned value between module instances (jobs).
   129  
   130  Usually charts initialized in `Init` and `Chart` method just returns the charts instance:
   131  
   132  ```
   133  // example.go
   134  
   135  func (e *Example) Charts() *Charts {
   136      return e.charts
   137  }
   138  ```
   139  
   140  ### Collect method
   141  
   142  - `Collect` collects metrics.
   143  - Called only if `Check` returned `true`.
   144  - Called every `update_every` seconds.
   145  - `map[string]int64` keys are charts dimensions ids'.
   146  
   147  We propose to use the following template:
   148  
   149  ```
   150  // example.go
   151  
   152  func (e *Example) Collect() map[string]int64 {
   153      ms, err := e.collect()
   154      if err != nil {
   155          e.Error(err)
   156      }
   157  
   158      if len(ms) == 0 {
   159          return nil
   160      }
   161      return ms
   162  }
   163  ```
   164  
   165  Move metrics collection logic into the `collect.go` file. See [suggested module layout](#module-Layout).
   166  
   167  ### Cleanup method
   168  
   169  - `Cleanup` performs the job cleanup/teardown.
   170  - Called if `Init` or `Check` fails, or we want to stop the job after `Collect`.
   171  
   172  If you have nothing to clean up:
   173  
   174  ```
   175  // example.go
   176  
   177  func (Example) Cleanup() {}
   178  ```
   179  
   180  ## Module Layout
   181  
   182  The general idea is to not put everything in a single file.
   183  
   184  We recommend using one file per logical area. This approach makes it easier to maintain the module.
   185  
   186  Suggested minimal layout:
   187  
   188  | Filename                                          | Contains                                               |
   189  |---------------------------------------------------|--------------------------------------------------------|
   190  | [`module_name.go`](#file-module_namego)           | Module configuration, implementation and registration. |
   191  | [`charts.go`](#file-chartsgo)                     | Charts, charts templates and constructor functions.    |
   192  | [`init.go`](#file-initgo)                         | Initialization methods.                                |
   193  | [`collect.go`](#file-collectgo)                   | Metrics collection implementation.                     |
   194  | [`module_name_test.go`](#file-module_name_testgo) | Public methods/functions tests.                        |
   195  | [`testdata/`](#file-module_name_testgo)           | Files containing sample data.                          |
   196  
   197  ### File `module_name.go`
   198  
   199  > :exclamation: See the example [`example.go`](https://github.com/netdata/go.d.plugin/blob/master/modules/example/example.go).
   200  
   201  Don't overload this file with the implementation details.
   202  
   203  Usually it contains only:
   204  
   205  - module registration.
   206  - module configuration.
   207  - [module interface implementation](#module-interface).
   208  
   209  ### File `charts.go`
   210  
   211  > :exclamation: See the example: [`charts.go`](https://github.com/netdata/go.d.plugin/blob/master/modules/example/charts.go).
   212  
   213  Put charts, charts templates and charts constructor functions in this file.
   214  
   215  ### File `init.go`
   216  
   217  > :exclamation: See the example: [`init.go`](https://github.com/netdata/go.d.plugin/blob/master/modules/example/init.go).
   218  
   219  All the module initialization details should go in this file.
   220  
   221  - make a function for each value that needs to be initialized.
   222  - a function should return a value(s), not implicitly set/change any values in the main struct.
   223  
   224  ```
   225  // init.go
   226  
   227  // Prefer this approach.
   228  func (e Example) initSomeValue() (someValue, error) {
   229      // ...
   230      return someValue, nil 
   231  }
   232  
   233  // This approach is ok too, but we recommend to not use it.
   234  func (e *Example) initSomeValue() error {
   235      // ...
   236      m.someValue = someValue
   237      return nil
   238  }
   239  ```     
   240  
   241  ### File `collect.go`
   242  
   243  > :exclamation: See the example: [`collect.go`](https://github.com/netdata/go.d.plugin/blob/master/modules/example/collect.go).
   244  
   245  This file is the entry point for the metrics collection.
   246  
   247  Feel free to split it into several files if you think it makes the code more readable.
   248  
   249  Use `collect_` prefix for the filenames: `collect_this.go`, `collect_that.go`, etc.
   250  
   251  ```
   252  // collect.go
   253  
   254  func (e *Example) collect() (map[string]int64, error) {
   255      collected := make(map[string])int64
   256      // ...
   257      // ...
   258      // ...
   259      return collected, nil
   260  }
   261  ```
   262  
   263  ### File `module_name_test.go`
   264  
   265  > :exclamation: See the example: [`example_test.go`](https://github.com/netdata/go.d.plugin/blob/master/modules/example/example_test.go).
   266  
   267  > if you have no experience in testing we recommend starting with [testing package documentation](https://golang.org/pkg/testing/).
   268  
   269  > we use `assert` and `require` packages from [github.com/stretchr/testify](https://github.com/stretchr/testify) library,
   270  > check [their documentation](https://pkg.go.dev/github.com/stretchr/testify).
   271  
   272  Testing is mandatory.
   273  
   274  - test only public functions and methods (`New`, `Init`, `Check`, `Charts`, `Cleanup`, `Collect`).
   275  - do not create a test function per a case, use [table driven tests](https://github.com/golang/go/wiki/TableDrivenTests)
   276    . Prefer `map[string]struct{ ... }` over `[]struct{ ... }`.
   277  - use helper functions _to prepare_ test cases to keep them clean and readable.
   278  
   279  ### Directory `testdata/`
   280  
   281  Put files with sample data in this directory if you need any. Its name should
   282  be [`testdata`](https://golang.org/cmd/go/#hdr-Package_lists_and_patterns).
   283  
   284  > Directory and file names that begin with "." or "_" are ignored by the go tool, as are directories named "testdata".
   285  
   286  ## Helper packages
   287  
   288  There are [some helper packages](https://github.com/netdata/go.d.plugin/tree/master/pkg) for writing a module.
   289