Table of contents

Introduction

Grafana is an open-source analytics and interactive visualization web application for creating charts, graphs, and alerts from diverse data sources. It helps to monitor metrics, logs, and traces across systems like servers, storage, and applications. Common use cases include dashboarding IT infrastructure, such as performance, CPU utilization, disk health, application status and anomaly detection.

Architecture

For my use case, Grafana’s backend (running on IBM i) will use Node.js to send data in JSON format to the Grafana frontend (running on Linux).

Grafana.png

Grafana Directory Structure

graph TD
	  A[Grafana Backend on IBM i] --> |main directory| B[**/opt/grafana/**]
    B --> |Node.js entry file| C[index.js]
    B --> |dependencies| D[package.json]
    D --> |project's manifest| E[package-lock.json]
    B --> |installed packages| F[**node_modules**]
    
  style A fill:#FFD8A8,stroke:#333,stroke-width:2px
  style B fill:#cce5ff,stroke:#333,stroke-width:2px
  style C fill:#ccffcc,stroke:#333,stroke-width:2px
  style D fill:#ccffcc,stroke:#333,stroke-width:2px
  style E fill:#fffdd0,stroke:#333,stroke-width:2px
  style F fill:#fffdd0,stroke:#333,stroke-width:2px

Configuration on client (Backend in IBM i)

Installing prerequisites

yum install ca-certificates-mozilla.noarch gzip tar-gnu nodejs22 wget

<aside>

Installing Grafana

mkdir -p /opt/grafana && cd /opt/grafana → create Grafana folder

For the panels and dashboard examples I provide, I will extract data from this SQL query

select HOST_NAME,
MACHINE_TYPE, MACHINE_MODEL, SERIAL_NUMBER, PARTITION_ID,
RESTRICTED_STATE, ATTENTION_LIGHT, IPL_MODE, IPL_TYPE,
SYSTEM_ASP_USED, SYSTEM_ASP_STORAGE, MAIN_STORAGE_SIZE,
DEFINED_MEMORY, MAXIMUM_MEMORY,
ACTIVE_JOBS_IN_SYSTEM, TOTAL_JOBS_IN_SYSTEM, MAXIMUM_JOBS_IN_SYSTEM
from **QSYS2.SYSTEM_STATUS_INFO**

touch index.js → main entry point for backend (handling HTTP requests and data queries)

const express = require("express");
const { dbconn, dbstmt } = require("idb-connector");

const PORT = process.env.PORT || 3333;

/// SQL query
const QSYS2_SYSTEM_STATUS_INFO = "select * from QSYS2.SYSTEM_STATUS_INFO";

const app = express();

/// CORS for Grafana
app.use((req, res, next) => {
  // Allow any origin (Grafana may be served from a different host)
  res.setHeader("Access-Control-Allow-Origin", "*");
  // Allow only GET and OPTIONS (endpoints are read-only)
  res.setHeader("Access-Control-Allow-Methods", "GET,OPTIONS");
  // Allow common request headers Grafana may send
  res.setHeader("Access-Control-Allow-Headers", "content-type, accept");
  // Respond to preflight requests immediately
  if (req.method === "OPTIONS") return res.sendStatus(204);
  next();
});

/// DB2 for i connection
// Create a new DB connection object
const connection = new dbconn();
// Open a local connection to DB2 for i
connection.conn("*LOCAL");
// Create a statement tied to the connection
const statement = new dbstmt(connection);
// Health-check route
app.get("/", (req, res) => res.status(200).send("OK"));

/// JSON API endpoint for Grafana
app.get("/QSYS2_SYSTEM_STATUS_INFO", (req, res) => {
  try {
    // Execute the SQL stored in the constant and return the rows synchronously
    const rows = statement.execSync(QSYS2_SYSTEM_STATUS_INFO);
    // Close the cursor/resources for the statement result set
    statement.closeCursor();
    // Return rows as JSON (or empty array if undefined)
    res.json(rows || []);
  } catch (err) {
    // Ensure cursor is closed on error (best-effort)
    try { statement.closeCursor(); } catch (e) {}
    // Return HTTP 500 with the error message
    res.status(500).json({ error: String(err) });
  }
});

// Start HTTP server on all interfaces
app.listen(PORT, "0.0.0.0", () => {
  console.log(`IBM i Grafana API is running on port ${PORT}`);
});