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