Custom Dashboards
Custom Dashboards let you create your own Grafana dashboards and charts, using the metrics QMonitor already collects for your organization. They live only in your organization, read only your organization’s data and are only visible to your users.
Creating a custom dashboard
- In the QMonitor side menu, open Custom Dashboards and click New dashboard (available to Premium users; the other users can view the custom dashboards).
- Give it a name and confirm. The dashboard opens in edit mode.
- Add a panel. The data source is already set to Metrics and cannot be changed: every custom dashboard reads from this single, read-only source.
- In the panel query, keep the Infinity query type and set the URL to
/followed by a measurement name (see below).
To delete a dashboard, use the trash icon next to it in the Custom Dashboards menu (Premium users).
To see what you can query, open Custom Dashboards → Metrics catalog: it lists every measurement with its fields and tags, the monitored instances, and a preview of the data.
How the Metrics API works
You never write a raw database query. You pick a measurement (one of the standard ones below, or one of your organization’s own Custom Queries) and pass structured parameters. QMonitor builds a safe, read-only query against your organization’s data.
The data source already points at the API, so in a panel the URL is just / and the
measurement name:
/custom_db_size
Use Grafana’s time macros so the panel follows the dashboard time picker:
/sqlserver_cpu?timeFrom=${__from}&timeTo=${__to}&aggregation=mean
Query parameters
| Parameter | Description |
|---|---|
timeFrom, timeTo | Time range: epoch milliseconds or ISO-8601. With ${__from} and ${__to} Grafana passes the dashboard time range and handles the time zone for you. Dates written by hand are read as UTC unless they carry an offset. from / to also work. Without them the last hour is returned; a value that cannot be read, or a timeFrom after timeTo, returns an error. |
aggregation | Aggregation applied to every field: mean (default), max, min, sum, last, first, count, median. Only one per request: to get, say, mean and max, add two queries to the panel. |
interval | Aggregation bucket, in seconds. Optional; by default it follows the time range (1 h: 1 min, 24 h: 5 min, 3 days: 10 min, 7 days: 30 min, longer: 1 h). It is never below the measurement’s interval shown in the catalog, nor below 60 seconds. |
fields | Comma-separated list of fields to return. Optional; defaults to all fields of the measurement. |
tags | Comma-separated list of tags to group by. Optional; defaults to all tags of the measurement, so every instance gets its own series. |
filter.<tag> | Keep only some tag values, e.g. filter.sql_instance=SRV01:MSSQL. Several values are comma-separated (filter.sql_instance=SRV01:MSSQL,SRV02:MSSQL) and match any of them; repeat the parameter for more tags. An empty value is ignored. |
You can write the parameters directly in the URL, as in the examples below, or add them one by
one in the Infinity query under Headers, Request params → URL Query Param. Always pass
timeFrom=${__from}&timeTo=${__to}, otherwise the panel shows only the last hour.
Examples
CPU trend of every instance: one line per instance, one point every few minutes depending on the time range.
/sqlserver_cpu?timeFrom=${__from}&timeTo=${__to}&fields=sqlserver_process_cpu&tags=sql_instance
Rows come back one per instance and time bucket. To get one line per instance in a Time series
panel, add the Partition by values transformation on sql_instance.
Instances chosen from a dashboard variable: create a variable named instance of type
Query on the Metrics data source with URL /instances (single or multi-select, “All” included),
then filter on it. Use the :csv format so a multi-select passes all selected values.
/sqlserver_cpu?timeFrom=${__from}&timeTo=${__to}&fields=sqlserver_process_cpu&filter.sql_instance=${instance:csv}
Azure SQL elastic pools: the values of any tag can feed a variable with URL
/<measurement>/tags/<tag>, for example /sqlazure_resource_stats/tags/elastic_pool_name
for a pool variable, then:
/sqlazure_resource_stats?timeFrom=${__from}&timeTo=${__to}&fields=avg_cpu_percent&tags=database_name&filter.elastic_pool_name=${pool:csv}
Current value for a Stat or Gauge panel: last returns the latest value of each bucket;
set the panel calculation to Last *.
/sqlserver_server_properties?timeFrom=${__from}&timeTo=${__to}&fields=uptime,db_online&tags=sql_instance&aggregation=last
Peaks instead of averages: with mean short spikes disappear on long ranges; max keeps
them. Here every bucket is 5 minutes long.
/sqlserver_cpu?timeFrom=${__from}&timeTo=${__to}&fields=sqlserver_process_cpu&filter.sql_instance=${instance:csv}&aggregation=max&interval=300
One performance counter: sqlserver_performance contains hundreds of counters, so narrow
it with filters, otherwise the query returns too many series.
/sqlserver_performance?timeFrom=${__from}&timeTo=${__to}&fields=value&tags=sql_instance&filter.sql_instance=${instance:csv}&filter.object=SQLServer:Buffer Manager&filter.counter=Page life expectancy&aggregation=last
Wait statistics of one category, split by wait type:
/sqlserver_waitstats?timeFrom=${__from}&timeTo=${__to}&fields=wait_time_ms&filter.sql_instance=${instance}&filter.wait_category=CPU&tags=wait_type&aggregation=max
One of your Custom Queries: same parameters, with the fields and tags you declared. Here the size of each database of an instance:
/custom_db_usage?timeFrom=${__from}&timeTo=${__to}&fields=data_mb,log_mb&tags=database_name&filter.sql_instance=${instance}&aggregation=last
ISO dates: useful when the time range comes from another source. Use UTC (Z): a +
in an offset such as +02:00 must be written as %2B in the URL.
/sqlserver_cpu?timeFrom=2026-10-01T00:00:00Z&timeTo=2026-10-02T00:00:00Z&fields=sqlserver_process_cpu&tags=sql_instance
If a request is not valid the panel shows the reason, for example an unknown field or tag, an unreadable date, a time range longer than 30 days, or too many series.
Endpoints
| URL | Returns |
|---|---|
/<measurement> | The data of a measurement, with the parameters above. |
/catalog | Every measurement available to your organization, with fields and tags. |
/instances | The monitored instances (sql_instance), for dashboard variables. |
/<measurement>/tags/<tag> | The values of one tag of a measurement, for dashboard variables. |
All of them return a JSON array (Infinity “table” format).
A measurement query returns one object per time bucket, per series: the tags you grouped
by, organization, a time field (ISO-8601, UTC) and each requested field already aggregated.
[
{ "sql_instance": "SRV01:MSSQL", "organization": "acme", "time": "2026-10-20T10:00:00Z", "sqlserver_process_cpu": 2.3 },
{ "sql_instance": "SRV01:MSSQL", "organization": "acme", "time": "2026-10-20T10:01:00Z", "sqlserver_process_cpu": 2.1 }
]
Which tag columns appear depends on tags (by default, all of the measurement’s tags); which
value columns appear depends on fields (by default, all of the measurement’s fields).
The catalog returns one object per available measurement:
[
{
"source": "standard",
"measurement": "sqlserver_cpu",
"description": "SQL Server vs other-process CPU usage (%).",
"interval": "60s",
"fields": ["sqlserver_process_cpu", "other_process_cpu"],
"tags": ["sql_instance", "host"]
}
]
source is standard for native measurements or custom for your organization’s own
Custom Queries; interval is how often the measurement is collected: buckets are never smaller
than this, nor than 60 seconds.
/instances and /<measurement>/tags/<tag> return one object per value, named after the tag:
[
{ "sql_instance": "SRV01:MSSQL" },
{ "sql_instance": "SRV02:MSSQL" }
]
Standard measurements
These native QMonitor measurements are available to every organization. In a panel, type
/ and the measurement name as the URL.
| Measurement | Description | Fields | Tags |
|---|---|---|---|
sqlserver_cpu | SQL Server vs other-process CPU usage (%). | sqlserver_process_cpu, other_process_cpu | sql_instance, host |
sqlserver_waitstats | Wait statistics by wait type. | wait_time_ms, waiting_tasks_count, max_wait_time_ms | sql_instance, wait_type, wait_category |
sqlserver_database_io | Per-file I/O: reads, writes, bytes and latency. | reads, writes, read_bytes, write_bytes, read_latency_ms, write_latency_ms | sql_instance, database_name, logical_filename, file_type |
sqlserver_volume_space | Disk volume space (bytes). | total_space_bytes, available_space_bytes, used_space_bytes | sql_instance, volume_mount_point |
sqlserver_performance | Raw performance counters (filter by the counter/instance tags). | value | sql_instance, object, counter, instance |
sqlserver_memory_clerks | Memory usage by memory clerk type (KB). | size_kb | sql_instance, clerk_type |
sqlserver_server_properties | Instance properties: uptime, cpu count, memory, database counts. | uptime, cpu_count, server_memory, db_online, db_offline | sql_instance, host |
sqlazure_resource_stats | Azure SQL resource usage (%). | avg_cpu_percent, avg_data_io_percent, avg_log_write_percent, dtu_consumption_percent | sql_instance, database_name, elastic_pool_name |
Your organization’s own Custom Queries appear in the catalog alongside these, with the measurement, fields and tags you defined for them.
Limits
To keep the shared platform healthy, each query is capped: the time range cannot exceed
30 days, each query must complete within 3 seconds, data is always aggregated into buckets
of at least 60 seconds, and a single
query returns at most 10,000 points per series and at most 50 series. When a range would
exceed 10,000 points the bucket is widened automatically, so the data comes back aggregated
instead of cut. More than 50 series returns an error: narrow it with filter.<tag> or group
by fewer tags.
Custom Queries for dashboards
A Custom Query appears in the catalog as soon as it is enabled; it starts producing data once it is assigned to an agent, after one collection interval. A few rules keep it working:
- Return numbers as integer or float: wrap decimals in
CAST(... AS float), otherwise they are stored as text. - Never change the type of an existing column: rows with a different type are discarded. To change it, give the column a new name.
- Name the instance like the standard measurements:
REPLACE(@@SERVERNAME, CHAR(92), ':') AS sql_instance. - On Azure SQL Database objects such as
sys.master_filesare not available: run that part only on other editions withIF SERVERPROPERTY('EngineEdition') <> 5 EXEC('...').