Skip to content

Commit

Permalink
Add api process list endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
LauJosefsen committed Apr 15, 2024
1 parent f7e393d commit 8dade7d
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 113 deletions.
119 changes: 119 additions & 0 deletions src/lib/transaction_repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { Instance } from '@/lib/config'
import mysql from 'mysql2/promise'
import { RowDataPacket } from 'mysql2/promise'

export type TransactionInfoDict = {
[threadId: number]: TransactionInfo
}

export type TransactionInfo = {
activeTime: number
info: string[]
}

export type Process = {
Id: number
User: string
Host: string
db: string | null
Command: string
Time: number
State: string
Info: string
Progress: number
}

export type ProcessWithTransaction = Process & {
transaction: TransactionInfo | null
}

const parseInnoDbStatus = (innoDbStatus: string): TransactionInfoDict => {
const splitInnoDbStatus = innoDbStatus.split('\n') // Find the line LIST OF TRANSACTIONS FOR EACH SESSION:\n
const transactionsStartIndex = splitInnoDbStatus.findIndex((line) => line.includes('LIST OF TRANSACTIONS FOR EACH SESSION:'))

// After the transactionStartIndex, read transactions lines, splitting by lines starting with ---TRANSACTION, until we meet the line --------\n
const transactions: TransactionInfoDict = {}

let transaction: TransactionInfo = {
activeTime: -1,
info: [],
}

for (let i = transactionsStartIndex; i < splitInnoDbStatus.length; i++) {
const line = splitInnoDbStatus[i]

if (line === undefined) {
continue
}

if (line.startsWith('--------')) {
break
}

if (line.startsWith('---TRANSACTION')) {
// Get the active time from the format '..., ACTIVE 1 sec'
const index = line.indexOf(', ACTIVE')
const activeTime = parseInt(line.slice(index + 8))

transaction = {
activeTime,
info: [],
}
}

if (line.startsWith('MariaDB thread id')) {
// Get the thread id from the format `MariaDB thread id 3, ...`
const threadId = parseInt(line.split(' ')[3])

transactions[threadId] = transaction
}

transaction.info.push(line)
}

return transactions
}

export const getTransactionsAndInnoDBStatus = async (instance: Instance): Promise<[ProcessWithTransaction[], string]> => {
let conn: mysql.Connection | null = null

let processList: Process[] = []
let innoDbStatusString = ''

try {
conn = await mysql.createConnection(instance)
const [processListResult] = await conn.query('SHOW PROCESSLIST;')
processList = processListResult as Process[]
const [innoDbStatusResult] = await conn.query<RowDataPacket[]>('SHOW ENGINE INNODB STATUS;')

innoDbStatusString = innoDbStatusResult[0]['Status'] as string
} finally {
conn?.end()
}

const innoDbStatus = parseInnoDbStatus(innoDbStatusString)

const processListWithTransaction: ProcessWithTransaction[] = processList.map((process) => {
const transaction = innoDbStatus[process.Id] || null
return {
...process,
transaction,
}
})

// Order by transaction.activeTime desc, then by process.Time desc
processListWithTransaction.sort((a, b) => {
if (a.transaction && !b.transaction && a.transaction.activeTime > 10) {
return -1
}
if (!a.transaction && b.transaction && b.transaction.activeTime > 10) {
return 1
}
if (a.transaction && b.transaction) {
return b.transaction.activeTime - a.transaction.activeTime
}
return b.Time - a.Time
})

return [processListWithTransaction, innoDbStatusString]
}
28 changes: 28 additions & 0 deletions src/pages/api/processlist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { getConfig } from '@/lib/config'
import mysql from 'mysql2/promise'
import { getTransactionsAndInnoDBStatus } from '@/lib/transaction_repository'

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// Get instance identifer from the request query parameter
const { instance } = req.query

// If instance is array, return error
if (Array.isArray(instance)) {
return res.status(400).json({ error: 'instance query param must be a string' })
}

if (!instance) {
return res.status(400).json({ error: 'instance query param is required' })
}

const dbConfig = getConfig().instances[instance]

if (!dbConfig) {
return res.status(400).json({ error: 'instance not found' })
}

const [processList, innoDbStatus] = await getTransactionsAndInnoDBStatus(dbConfig)

res.status(200).json({ processList, innoDbStatus })
}
115 changes: 2 additions & 113 deletions src/pages/instance/[identifier]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,10 @@
import { GetServerSideProps, InferGetServerSidePropsType } from 'next'
import { RowDataPacket } from 'mysql2/promise'
import { useRouter } from 'next/router'
import Link from 'next/link'
import mysql from 'mysql2/promise'
import { getConfig } from '@/lib/config'
import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, getKeyValue, SortDescriptor } from '@nextui-org/react'
import React from 'react'

type TransactionInfoDict = {
[threadId: number]: TransactionInfo
}

type TransactionInfo = {
activeTime: number
info: string[]
}

type Process = {
Id: number
User: string
Host: string
db: string | null
Command: string
Time: number
State: string
Info: string
Progress: number
}

type ProcessWithTransaction = Process & {
transaction: TransactionInfo | null
}
import { getTransactionsAndInnoDBStatus, ProcessWithTransaction } from '@/lib/transaction_repository'

type Repo = {
processList: ProcessWithTransaction[]
Expand Down Expand Up @@ -61,53 +35,6 @@ const blackOrWhite = function (hex: string): string {
return brightness > 155 ? '#000000' : '#ffffff'
}

const parseInnoDbStatus = (innoDbStatus: string): TransactionInfoDict => {
const splitInnoDbStatus = innoDbStatus.split('\n') // Find the line LIST OF TRANSACTIONS FOR EACH SESSION:\n
const transactionsStartIndex = splitInnoDbStatus.findIndex((line) => line.includes('LIST OF TRANSACTIONS FOR EACH SESSION:'))

// After the transactionStartIndex, read transactions lines, splitting by lines starting with ---TRANSACTION, until we meet the line --------\n
const transactions: TransactionInfoDict = {}

let transaction: TransactionInfo = {
activeTime: -1,
info: [],
}

for (let i = transactionsStartIndex; i < splitInnoDbStatus.length; i++) {
const line = splitInnoDbStatus[i]

if (line === undefined) {
continue
}

if (line.startsWith('--------')) {
break
}

if (line.startsWith('---TRANSACTION')) {
// Get the active time from the format '..., ACTIVE 1 sec'
const index = line.indexOf(', ACTIVE')
const activeTime = parseInt(line.slice(index + 8))

transaction = {
activeTime,
info: [],
}
}

if (line.startsWith('MariaDB thread id')) {
// Get the thread id from the format `MariaDB thread id 3, ...`
const threadId = parseInt(line.split(' ')[3])

transactions[threadId] = transaction
}

transaction.info.push(line)
}

return transactions
}

export const getServerSideProps = (async (context) => {
const instance = getConfig().instances[context.query.identifier as string]

Expand All @@ -120,45 +47,7 @@ export const getServerSideProps = (async (context) => {
}
}

let conn: mysql.Connection | null = null

let processList: Process[] = []
let innoDbStatusString = ''

try {
conn = await mysql.createConnection(instance)
const [processListResult] = await conn.query('SHOW PROCESSLIST;')
processList = processListResult as Process[]
const [innoDbStatusResult] = await conn.query<RowDataPacket[]>('SHOW ENGINE INNODB STATUS;')

innoDbStatusString = innoDbStatusResult[0]['Status'] as string
} finally {
conn?.end()
}

const innoDbStatus = parseInnoDbStatus(innoDbStatusString)

const processListWithTransaction: ProcessWithTransaction[] = processList.map((process) => {
const transaction = innoDbStatus[process.Id] || null
return {
...process,
transaction,
}
})

// Order by transaction.activeTime desc, then by process.Time desc
processListWithTransaction.sort((a, b) => {
if (a.transaction && !b.transaction && a.transaction.activeTime > 10) {
return -1
}
if (!a.transaction && b.transaction && b.transaction.activeTime > 10) {
return 1
}
if (a.transaction && b.transaction) {
return b.transaction.activeTime - a.transaction.activeTime
}
return b.Time - a.Time
})
const [processListWithTransaction, innoDbStatusString] = await getTransactionsAndInnoDBStatus(instance)

const repo: Repo = {
processList: processListWithTransaction,
Expand Down

0 comments on commit 8dade7d

Please sign in to comment.