Connect Custom Data Sources — Consolidated Reference

Connect Custom Data Sources — Consolidated Reference

Connect Custom Data Sources — Consolidated Reference

Purpose: Single-document reference for vibe coding against the Eyer custom data source integration. Consolidates all 8 pages from the "Connect Custom Data Sources" section of the Eyer Knowledge Base. Last synced: 2026-04-03.


1. Overview (README FIRST)

Data can come from an open source agent, custom code, scripts, or instrumented existing code.

Two paths:

  • Path A — Open source / custom agent (no format control): You need a mapping from your agent's JSON output to the Eyer internal format. Follow Section 1 below.

  • Path B — Instrumented code (you control output format): Skip mapping. Format directly to the Eyer generic format. Follow Section 2 below.

Path A — Step by step

  1. Get your metrics with names and timestamps in a flat JSON format. Less nesting = easier mapping.

  2. Structure data with metrics/values in a fields section and metadata in a tags section. The fields section can repeat (e.g., one per database).

  3. Create a mapping from your data to the Eyer internal format (see mapping examples below).

  4. Validate the mapping via the Push API test endpoint.

  5. Contact Eyer to enable your custom endpoint.

Example flat JSON (Telegraf output):

{ "metrics": [ { "fields": { "disk_reads_mean": 1.012, "disk_writes_per_sec_mean": 0.857, "last_Run_mean": 1740047577489, "log_bytes_flushed_mean": 3.959, "logical_reads_mean": 914.996, "total_sessions_mean": 14, "waits_total": 0, "waits_total_mean": 7.3 }, "name": "dbwatch", "tags": { "host": "testmachine1", "instance": "DatabaseServer1" }, "timestamp": 1740047760 } ] }

Path B — Step by step

  1. Refer to the Eyer generic input format (Section 5 below).

  2. Structure your output accordingly.

  3. Contact Eyer to enable your custom endpoint.


2. Input Data Format Requirements

Core rules

  1. Eyer ingests time series data: sequential data points with timestamps and one or more measured values.

  2. All data must be in JSON format. Prefer flatter structures with limited nesting.

  3. Timestamps must be in epoch (1745352000000) or ISO (2025-11-11T11:00:00Z) format.

  4. Recommended resolution: 1-minute aggregates before sending. Eyer's minimum operating resolution is 1 minute.

  5. If local aggregation is not feasible, send raw data. Eyer will aggregate automatically to per-minute.

  6. If no aggregation function is specified, Eyer defaults to average.

JSON examples

Example 1 — Database metrics (Telegraf):

{ "metrics": [ { "fields": { "disk_reads_mean": 1.012, "disk_writes_per_sec_mean": 0.857, "last_Run_mean": 1740047577489, "log_bytes_flushed_mean": 3.959, "logical_reads_mean": 914.996, "total_sessions_mean": 14, "waits_total": 0, "waits_total_mean": 7.3 }, "name": "dbwatch", "tags": { "host": "testmachine1", "instance": "Database1", "url": "http://localhost:8080/dashboard/dbWatch%2001/data/HkRmUaLz0Z.telegraf.json" }, "timestamp": 1740047760 } ] }

Example 2 — SNMP traffic (repeating ports):

{ "metrics": [ { "fields": { "ifHCInOctets": 0, "ifHCOutOctets": 0 }, "name": "Octets_field", "tags": { "agent_host": "192.168.88.54", "host": "DESKTOP-S01F7CP", "hostname": "US-8-150W", "ifName": "3/26" }, "timestamp": 1741167360 }, { "fields": { "ifHCInOctets": 0, "ifHCOutOctets": 0 }, "name": "Octets_field", "tags": { "agent_host": "192.168.88.54", "host": "DESKTOP-S01F7CP", "hostname": "US-8-150W", "ifName": "3/11" }, "timestamp": 1741167360 } ] }

Example 3 — Prometheus flat array:

[ { "fields": { "value": 100 }, "name": "prometheus", "tags": { "field_type": "go_gc_gogc_percent", "host": "eyer-server-4.local", "url": "http://localhost:9100/metrics" }, "timestamp": 1763897800 }, { "fields": { "value": 7 }, "name": "prometheus", "tags": { "field_type": "go_goroutines", "host": "eyer-server-4.local", "url": "http://localhost:9100/metrics" }, "timestamp": 1763897800 } ]

3. Mapping Example 1 — One Node Per Repeating Field (new node per instance)

Use case: Each repeating fields entry creates a new node with its own metrics, differentiated by a tag field.

Input (Telegraf database metrics):

{ "metrics": [ { "fields": { "disk_reads_mean": 1.012, "disk_writes_per_sec_mean": 0.857, "log_bytes_flushed_mean": 3.959, "logical_reads_mean": 914.996, "total_sessions_mean": 14, "waits_total_mean": 7.3 }, "name": "dbwatch", "tags": { "host": "testmachine1", "instance": "Database1", "url": "http://localhost:8080/dashboard/dbWatch%2001/data/HkRmUaLz0Z.telegraf.json" }, "timestamp": 1740047760 } ] }

Mapping JSON:

{ "selector": ["tags.host", "tags.instance", "timestamp"], "type": "telegraf.waitstats", "id_selector": "tags.instance", "root": "metrics", "system": "tags.host", "mappings": [ { "type": "telegraf.waitstats.waits", "guid": "16e41dba-e6a4-4427-b753-cf6eee41e384", "name": "dbWatch waits", "timestamp": "timestamp", "fields": [ { "name": "fields.disk_reads_mean", "display": "Disk reads", "type": "float", "aggregation": "avg", "guid": "9ed41a78-3094-4e41-98b5-b1f98f63dc3a" }, { "name": "fields.disk_writes_per_sec_mean", "display": "Disk writes per second", "type": "float", "aggregation": "avg", "guid": "1203a428-0b80-4240-8c1b-f7fa2665ec22" }, { "name": "fields.log_bytes_flushed_mean", "display": "Log bytes flushed", "type": "float", "aggregation": "avg", "guid": "2add293a-446a-468e-91c3-8f164fe697c8" }, { "name": "fields.logical_reads_mean", "display": "Logical reads", "type": "float", "aggregation": "avg", "guid": "7acb184d-c4ce-4287-ab4a-f63f15976bac" }, { "name": "fields.total_sessions_mean", "display": "Total sessions", "type": "int", "aggregation": "avg", "guid": "30c71236-48ad-4fa8-b282-f587ffe8b4b9" }, { "name": "fields.waits_total_mean", "display": "Waits total", "type": "float", "aggregation": "avg", "guid": "1f2a5a97-338b-4a92-8846-f27cd9f6b71f" } ], "name_map": "tags.instance" } ], "skip_merge": true }

Mapping field reference:

Field

Description

Field

Description

selector

Array of fields used to regroup data

type

Internal type string; suffix used to register API endpoint (e.g. /api/telegraf/waitstats). Must be alpha.

id_selector

Tag used for node uniqueness checks, combined with name_map

root

Root object to start parsing from

system

Field to populate the System value

mappings

Array of node type definitions; each element = one node type with N stat types

mappings[].type

Internal node type ID. Must be unique. Prefixed with top-level type.

mappings[].guid

Optional UUID per node type. Auto-generated from type if omitted.

mappings[].name

User-friendly node type name

mappings[].timestamp

Field path to pick timestamp from

mappings[].fields[].name

Internal stat name; must correspond to input field path

mappings[].fields[].display

User-facing display name

mappings[].fields[].type

Data type string (e.g. float, int, Count)

mappings[].fields[].aggregation

avg, max, or sum

mappings[].fields[].guid

Optional UUID per stat type. Auto-deduced from name if omitted.

mappings[].name_map

Tag field to use as the node name

skip_merge

true = parse each array entry separately. false = merge all fields first then repartition.

Output (Eyer internal format):

[ { "Node": { "Name": "Database1", "SystemName": "testmachine1", "AgentId": "telegraf.waitstats", "AtomId": "Database1" }, "NodeType": { "Id": "16e41dba-e6a4-4427-b753-cf6eee41e384", "Name": "telegraf.waitstats.waits", "DisplayName": "Database waits" }, "Stats": [ { "Name": "fields.disk_reads_mean", "Type": "float", "Guid": "9ed41a78-3094-4e41-98b5-b1f98f63dc3a", "Aggregation": "avg", "Display": "Disk reads", "Value": 1.012, "Timestamp": 1740047760 }, { "Name": "fields.disk_writes_per_sec_mean", "Type": "float", "Guid": "1203a428-0b80-4240-8c1b-f7fa2665ec22", "Aggregation": "avg", "Display": "Disk writes per second", "Value": 0.857, "Timestamp": 1740047760 }, { "Name": "fields.total_sessions_mean", "Type": "int", "Guid": "30c71236-48ad-4fa8-b282-f587ffe8b4b9", "Aggregation": "avg", "Display": "Total sessions", "Value": 14, "Timestamp": 1740047760 }, { "Name": "fields.waits_total_mean", "Type": "float", "Guid": "1f2a5a97-338b-4a92-8846-f27cd9f6b71f", "Aggregation": "avg", "Display": "Waits total", "Value": 7.3, "Timestamp": 1740047760 } ] } ]

4. Mapping Example 2 — All Metrics on One Node (flatten into single node)

Use case: All repeating fields entries collapse onto the same node when the hostname tag is identical. Contrast with Example 1 which creates one node per instance.

Input (Telegraf SNMP traffic per switch port):

{ "metrics": [ { "fields": { "ifHCInOctets": 0, "ifHCOutOctets": 0 }, "name": "Octets_field", "tags": { "agent_host": "192.168.88.54", "host": "DESKTOP-S01F7CP", "hostname": "US-8-150W", "ifName": "3/26" }, "timestamp": 1741167360 }, { "fields": { "ifHCInOctets": 0, "ifHCOutOctets": 0 }, "name": "Octets_field", "tags": { "agent_host": "192.168.88.54", "host": "DESKTOP-S01F7CP", "hostname": "US-8-150W", "ifName": "3/11" }, "timestamp": 1741167360 } ] }

Mapping JSON:

{ "selector": ["tags.hostname", "timestamp"], "type": "booomi.telegraf.snmp", "id_selector": "tags.hostname", "root": "metrics", "system": "tags.hostname", "skip_merge": true, "mappings": [ { "type": "booomi.telegraf.snmp.interface", "name": "SNMP Device", "guid": "7f92fc9c-23a3-462a-a556-9a7da1694e4a", "timestamp": "timestamp", "fields": [ { "name": "fields.ifHCInOctets", "name_map": "tags.ifName", "display": "In Octets", "type": "Count", "aggregation": "avg", "cumulative": true }, { "name": "fields.ifHCOutOctets", "name_map": "tags.ifName", "display": "Out Octets", "type": "Count", "aggregation": "avg", "cumulative": true } ], "name_map": "tags.hostname" } ] }

Key difference vs Example 1: name_map is on the field level (not mapping level), which makes each field's name dynamically suffixed with the tag value (tags.ifName), collapsing all ports onto a single node.

Output (Eyer internal format):

[ { "Node": { "Name": "US-8-150W", "SystemName": "US-8-150W", "AgentId": "booomi.telegraf.snmp", "AtomId": "US-8-150W" }, "NodeType": { "Id": "7f92fc9c-23a3-462a-a556-9a7da1694e4a", "Name": "booomi.telegraf.snmp.interface", "DisplayName": "SNMP Device" }, "Stats": [ { "Name": "fields.ifHCInOctets", "Type": "Count", "Aggregation": "avg", "Display": "In Octets", "Value": 0, "Timestamp": 1741167360 }, { "Name": "fields.ifHCOutOctets", "Type": "Count", "Aggregation": "avg", "Display": "Out Octets", "Value": 0, "Timestamp": 1741167360 } ] } ]

5. Eyer Generic Input Format (no mapping needed)

Use this when you control the output format of your data source. No mapping file required — format directly to this schema and send to Eyer.

Rules

  • All fields are mandatory

  • NodeType must not contain spaces

  • Timestamp must be in UNIX epoch format

  • Aggregation values: avg, max, sum

Example

In this example: System = "Server 1", two node types (disk and CPU), multiple stat types per node.

{ "metrics": [ { "Generic": "HD 1", "Time": 1742548364.371, "Timestamp": 1742548364, "NodeType": "server.disk", "StatType": "I/O", "System": "Server 1", "Value": 10, "Aggregation": "avg" }, { "Generic": "HD 1", "Time": 1742548364.371, "Timestamp": 1742548364, "NodeType": "server.disk", "StatType": "Utilization", "System": "Server 1", "Value": 12976534, "Aggregation": "avg" }, { "Generic": "CPU 1", "Time": 1742548364.371, "Timestamp": 1742548364, "NodeType": "server.cpu", "StatType": "Usage", "System": "Server 1", "Value": 34, "Aggregation": "avg" } ] }

To add more nodes: replace HD 1 with HD 2, CPU 1 with CPU 2, etc.

Once output is ready, send a sample to Eyer (support email or Discord) to get your endpoint enabled.


6. Mapping Example 3 — Using an LLM to Generate Mappings

An LLM (Claude, ChatGPT, Gemini, Mistral) can generate mapping JSON from your input data. The prompt pattern is:

  1. Provide an example input JSON (your source data)

  2. Provide an example mapping JSON (from Example 1 or 2 above)

  3. Provide the expected output JSON (the mapped result)

  4. Then ask: "How would the mapping look for this new input?"

Critical: Choose the example that matches the node topology you want. If you feed Example 1 to the LLM, it will produce one-node-per-instance mappings. If you feed Example 2, it will collapse to a single node.

LLM prompt template:

Learn how to do a JSON mapping. You have the following input file: [PASTE YOUR EXAMPLE INPUT JSON] The JSON mapping for it is: [PASTE EXAMPLE MAPPING JSON] The resulting output is: [PASTE EXAMPLE OUTPUT JSON] Question: How would the mapping for the following new input look? [PASTE YOUR ACTUAL INPUT JSON]

Resulting LLM output for an SNMP interface input (using Example 1 as reference):

{ "selector": ["tags.host", "tags.ifName", "timestamp"], "type": "telegraf.ifstats", "id_selector": "tags.ifName", "root": "metrics", "system": "tags.host", "mappings": [ { "type": "telegraf.ifstats.octets", "guid": "b5d4d7c9-8f52-4df5-8101-ec5c63a8a123", "name": "Interface Octets", "timestamp": "timestamp", "fields": [ { "name": "fields.ifInOctets", "display": "Inbound Octets", "type": "int", "aggregation": "avg", "guid": "8f1c9de3-91ef-4a34-bc76-e2d05d2a6e51" }, { "name": "fields.ifOutOctets", "display": "Outbound Octets", "type": "int", "aggregation": "avg", "guid": "d10f4182-fc7c-4d65-a8c5-b72a2e6ec351" } ], "name_map": "tags.ifName" } ], "skip_merge": true }

Note: This LLM used Example 1 as the reference, so the result creates one node per switch port (tags.ifName). To get all ports on a single node, use Example 2 as the reference mapping instead.

After generating the mapping, always validate it with the Push API test endpoint (Section 7 below) before deploying.


7. Push API — Test and Validate Your Mapping

Use this endpoint to test a mapping against sample data before deploying. Returns the Eyer internal format so you can validate the node/metrics structure.

Endpoint:

POST https://eyer-prod-api-data-ne-stage.azurewebsites.net/api/mapping/test

Headers:

authenticate: <your apiTokenPush> Content-Type: application/json

Request body structure:

{ "mapping": { /* Your mapping JSON goes here */ }, "data": [ /* Your test input data goes here */ ] }

Full example request (copy-paste ready):

{ "mapping": { "selector": ["tags.host", "tags.instance", "timestamp"], "type": "telegraf.waitstats", "id_selector": "tags.instance", "root": "metrics", "system": "tags.host", "mappings": [ { "type": "telegraf.waitstats.waits", "guid": "16e41dba-e6a4-4427-b753-cf6eee41e384", "name": "Database waits", "timestamp": "timestamp", "fields": [ { "name": "fields.disk_reads_mean", "display": "Disk reads", "type": "float", "aggregation": "avg", "guid": "9ed41a78-3094-4e41-98b5-b1f98f63dc3a" }, { "name": "fields.disk_writes_per_sec_mean", "display": "Disk writes per second", "type": "float", "aggregation": "avg", "guid": "1203a428-0b80-4240-8c1b-f7fa2665ec22" }, { "name": "fields.log_bytes_flushed_mean", "display": "Log bytes flushed", "type": "float", "aggregation": "avg", "guid": "2add293a-446a-468e-91c3-8f164fe697c8" }, { "name": "fields.logical_reads_mean", "display": "Logical reads", "type": "float", "aggregation": "avg", "guid": "7acb184d-c4ce-4287-ab4a-f63f15976bac" }, { "name": "fields.total_sessions_mean", "display": "Total sessions", "type": "int", "aggregation": "avg", "guid": "30c71236-48ad-4fa8-b282-f587ffe8b4b9" }, { "name": "fields.waits_total_mean", "display": "Waits total", "type": "float", "aggregation": "avg", "guid": "1f2a5a97-338b-4a92-8846-f27cd9f6b71f" } ], "name_map": "tags.instance" } ], "skip_merge": true }, "data": { "metrics": [ { "fields": { "disk_reads_mean": 1.012, "disk_writes_per_sec_mean": 0.857, "last_Run_mean": 1740047577489, "log_bytes_flushed_mean": 3.959, "logical_reads_mean": 914.996, "total_sessions_mean": 14, "waits_total": 0, "waits_total_mean": 7.3 }, "name": "dbwatch", "tags": { "host": "testmachine1", "instance": "Database1", "url": "http://localhost:8080/dashboard/dbWatch%2001/data/HkRmUaLz0Z.telegraf.json" }, "timestamp": 1740047760 } ] } }

8. Eyer Internal Data Format and Structure

All custom data, once mapped, is stored in the Eyer node→metrics structure.

Hierarchy:

  • System — the host/server/device where the agent is installed

  • Node — a monitored entity within the system (e.g., a database, network port, hard drive). Multiple nodes per system.

  • Stats — one or more metrics per node (e.g., disk reads, CPU usage)

Internal format schema:

[ { "Node": { "Name": "<node name from name_map tag>", "SystemName": "<system name from system field>", "AgentId": "<top-level type from mapping>", "AtomId": "<same as Name>" }, "NodeType": { "Id": "<guid from mappings[].guid>", "Name": "<mappings[].type>", "DisplayName": "<mappings[].name>" }, "Stats": [ { "Name": "<fields[].name>", "Type": "<fields[].type>", "Guid": "<fields[].guid>", "Aggregation": "<fields[].aggregation>", "Display": "<fields[].display>", "Value": 0.0, "Timestamp": 0 } ] } ]

Concrete example (database server with one database node):

[ { "Node": { "Name": "Database1", "SystemName": "testmachine1", "AgentId": "telegraf.waitstats", "AtomId": "Database1" }, "NodeType": { "Id": "16e41dba-e6a4-4427-b753-cf6eee41e384", "Name": "telegraf.waitstats.waits", "DisplayName": "Database waits" }, "Stats": [ { "Name": "fields.disk_reads_mean", "Type": "float", "Guid": "9ed41a78-3094-4e41-98b5-b1f98f63dc3a", "Aggregation": "avg", "Display": "Disk reads", "Value": 1.012, "Timestamp": 1740047760 }, { "Name": "fields.disk_writes_per_sec_mean", "Type": "float", "Guid": "1203a428-0b80-4240-8c1b-f7fa2665ec22", "Aggregation": "avg", "Display": "Disk writes per second", "Value": 0.857, "Timestamp": 1740047760 }, { "Name": "fields.logical_reads_mean", "Type": "float", "Guid": "7acb184d-c4ce-4287-ab4a-f63f15976bac", "Aggregation": "avg", "Display": "Logical reads", "Value": 914.996, "Timestamp": 1740047760 }, { "Name": "fields.total_sessions_mean", "Type": "int", "Guid": "30c71236-48ad-4fa8-b282-f587ffe8b4b9", "Aggregation": "avg", "Display": "Total sessions", "Value": 14, "Timestamp": 1740047760 }, { "Name": "fields.waits_total_mean", "Type": "float", "Guid": "1f2a5a97-338b-4a92-8846-f27cd9f6b71f", "Aggregation": "avg", "Display": "Waits total", "Value": 7.3, "Timestamp": 1740047760 } ] } ]

Quick Decision Reference

Situation

Action

Situation

Action

You control the output format

Use generic input format (Section 5). No mapping needed.

Using an open source agent (Telegraf, Prometheus, etc.)

Create a mapping. Use Example 1 or 2 as base.

Each data entity should be a separate node

Use mapping pattern from Example 1 (name_map at mapping level)

All entities should share one node with dynamic metric names

Use mapping pattern from Example 2 (name_map at field level)

Not sure how to write the mapping JSON

Use LLM with the prompt pattern in Section 6

Mapping written — need to verify before going live

POST to validation endpoint (Section 7)

Endpoint not yet active

Contact Eyer via support email or Discord