How to graph my clients?

I have a few clients that send, receive or sync data. The relationships are sometimes complicated and I would love to see them as a graph between clients, data, and kind of connection (send, receive, sync).

Is there a way to do that one way or another? (short of drawing them myself)

No. A Syncthing device only has knowledge about its peers, not the entire network.

1 Like

Yes, I understand this but I was hoping that by analyzing the configuration of all my clients there wild be a way to lmao them that way.

Actually every node knows its own connected devices and shared folders, plus every other device directly sharing the same folder with one of the connected remote devices. In other words, the local perspective tells you every involved device that is one or two hops away. In a straight linear topology A - B - C - D, that means A can know about B (directly) and C (indirectly), but not D.

This info is all contained in the ClusterConfig messages exchanged each time a connection is established. However, Syncthing currently does not expose it in any way for analysis or plotting. Something I’d like to implement in the long term.

If you do find or develop a solution how to visualize device and folder relationships (even if generated from a manual description of the links), I’d be very interested so please do share it here in case.

1 Like

I would love to be able to use something like this too!

For now, I’ve always resorted to simply drawing the whole thing by hand, but the obvious disadvantage is that you need to re-do everything each time there are changes to the configuration. This could, however, be mitigated by drawing on a computer with a stylus (which I’ve actually got but I’m not a digital artist and drawing by hand still feels more natural to me :sweat_smile:).

@acolomb @tomasz86 I started to develop a very rudimentary tool that takes all the config.xml files that are available to someone (in my case several of them, plus a few on the family devices that I may or may not have access to).

My idea is to plot the basic relationships (who sends what where, and the type (synch, receive / send only)) via mermaid.js. Or something different if there is a better way.

I would love to automatically get the data via the API but this is very limitative and wild work only when all devices are within one network (which in my case could be possible, especially if I continuously try to pull the data).

The obvious drawback is that with the vibe files the plot is a one-shot.

That’s great to hear! I’ll definitely be looking forward to testing it :slightly_smiling_face:.

The config.xml approach is probably much more flexible. In my network there are devices located in a different country, so just querying the API is a no-go here.

This is not going to happen overnight as I am quite loaded with work, family and the need to finish my smart lock :slight_smile:

But I will add this to my parallelized development because it would really help me to ensure that the configuration is consistent and actually does what I think it does. I gave some thoughts this morning under the shower and I should bootstrap something “soon”, at least a very rough first draft.

Yes, being in another network is a showstopper.

I was considering sharing the configuration (as part of a synching folder share), send only, to a centralized machine but it will be a bit messy. I will see.

1 Like

something like:

package main

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
)

type Config struct {
	XMLName xml.Name `xml:"configuration"`
	Folders []Folder `xml:"folder"`
	Devices []Device `xml:"device"`
}

type Folder struct {
	XMLName xml.Name `xml:"folder"`
	Id      string   `xml:"id,attr"`
	Label   string   `xml:"label,attr"`
	Path    string   `xml:"path,attr"`
	Devices []Device `xml:"device"`
}

type Device struct {
	XMLName xml.Name `xml:"device"`
	Id      string   `xml:"id,attr"`
	Name    string   `xml:"name,attr"`
}

func main() {
	xmlFile, err := os.Open("config.xml")
	if err != nil {
		fmt.Println(err)
	}
	//fmt.Println("Successfully Opened xml")
	defer xmlFile.Close()

	byteValue, _ := ioutil.ReadAll(xmlFile)

	var conf Config // root
	xml.Unmarshal(byteValue, &conf)

	//fmt.Println("DEBUG", conf)

	/*
		for i := 0; i < len(conf.Folders); i++ {
			fmt.Println(conf.Folders[i])
		}
	*/

	fmt.Println("digraph map {\ngraph [root=\"Distance_0\",ratio=\"1\",rankdir = \"LR\"]")
	// prima estraggo i device coi nomi
	for _, d := range conf.Devices {
		fmt.Printf("\"%s\" [label=\"%s\"]\n", d.Id, d.Name)
	}

	// poi i folders coi link
	for _, f := range conf.Folders {
		fmt.Printf("\"%s\" [label=\"%s\"]\n", f.Id, f.Label)
		//fmt.Printf("\"%s\"\n", f.Id)
		for _, d := range f.Devices {
			fmt.Printf("\"%s\" -> \"%s\"\n", f.Id, d.Id)
		}
	}
	fmt.Println("}")
}

it generates a .dot file such as:

of course you can customize the program to generate different shapes for ‘devices’ and ‘folders’

with shapes:

package main

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
)

type Config struct {
	XMLName xml.Name `xml:"configuration"`
	Folders []Folder `xml:"folder"`
	Devices []Device `xml:"device"`
}

type Folder struct {
	XMLName xml.Name `xml:"folder"`
	Id      string   `xml:"id,attr"`
	Label   string   `xml:"label,attr"`
	Path    string   `xml:"path,attr"`
	Type    string   `xml:"type,attr"`
	Devices []Device `xml:"device"`
}

type Device struct {
	XMLName xml.Name `xml:"device"`
	Id      string   `xml:"id,attr"`
	Name    string   `xml:"name,attr"`
}

func main() {
	xmlFile, err := os.Open("config.xml")
	if err != nil {
		fmt.Println(err)
	}
	defer xmlFile.Close()

	byteValue, _ := ioutil.ReadAll(xmlFile)

	var conf Config // root
	xml.Unmarshal(byteValue, &conf)

	fmt.Println("DEBUG", conf)

	fmt.Println("digraph map {\ngraph [ratio=\"1\",rankdir = \"LR\"]")
	// prima estraggo i device coi nomi
	for _, d := range conf.Devices {
		fmt.Printf("\"%s\" [label=\"%s\",shape=box3d]\n", d.Id, d.Name)
	}

	// poi i folders coi link
	for _, f := range conf.Folders {
		fmt.Printf("\"%s\" [label=\"%s\",shape=folder]\n", f.Id, f.Label)
		//fmt.Printf("\"%s\"\n", f.Id)
		ty := "" // sendonly
		if f.Type == "sendreceive" {
			ty = "[dir=none]"
		}
		if f.Type == "receiveonly" {
			ty = "[dir=back]"
		}
		fmt.Println("DEBUG", f.Type, ty)

		for _, d := range f.Devices {
			fmt.Printf("\"%s\" -> \"%s\" %s\n", f.Id, d.Id, ty)
		}
	}
	fmt.Println("}")
}

You are faster than me :slight_smile:

Thanks - this is basically what I want to do, with the addition of

  • parsing a config file and its cert generates a JSON object that is indexed with the hostname. Several different configs (and certs) will generate several entries in the JSON which will allow to map several devices
  • I will add the direction (type) for each connection, as seen from both sides to check for consistency (when I have one folder as “receiveonly”, I usually have it as “sendonly” on the other side - but not always)
  • I will “pack” folders under a host to check for discrepancies in naming
  • my second example uses “type” to decide the arrow style
  • in dot you can group nodes (see DOT Language | Graphviz)

A very first version is here https://github.com/wsw70/syncthing-map

I will be back :slight_smile:

1 Like

Unfortunately, the URL seems to be broken :sweat_smile:. Is this a private repository by any chance?

Sorry, I just realized that I kept it as Private. Should be fixed now.

Great, thanks for sharing this! I hope it will keep getting developed and extended upon. If you like, we can add a pointer to the tool in the documentation.

I hope I’ll have time to try it out soon, already had some ideas what could be added, such as differing device names or being able to show when a folder is shared only one way, but not accepted on the other end.

And here is my proposal for a graphing tool:

It is a simpler ‘one-shot’ tool, give it a bunch of xml files and it spits the .dot

I’ve tried to use it under Windows, but unfortunately it doesn’t seem to be able to open the XML files. For the record, there are two of them in the folder.

.\syncthing-graph.exe *.xml > graph.dot
*** DEBUG prima del marshal {{ } [] []}
=== Processing file *.xml ( 1 of 1 )
open *.xml: The filename, directory name, or volume label syntax is incorrect.
*** DEBUG dopo marshal {{ } [] []}

The problem seems to be about the wildcard, because .\syncthing-graph.exe config1.xml config2.xml > graph.dot does work.