src/components/graphs/MemorySpecs/storageEngineMetrics.tsx (488 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ //@ts-nocheck /* eslint-disable react-hooks/exhaustive-deps */ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import GaugeChart from "react-gauge-chart"; import { Progress } from "@/components/ui/progress"; import { Database, Info, InfoIcon, RefreshCcw, RefreshCw } from "lucide-react"; import { useContext, useEffect, useState } from "react"; import { middlewareApi } from "@/lib/api"; import { Loader } from "@/components/ui/loader"; import { Button } from "@/components/ui/button"; import { Link } from "react-router-dom"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { NotFound } from "@/components/ui/not-found"; import { useToast } from "@/hooks/use-toast"; import { ModeType } from "@/components/toggle"; import { ModeContext } from "@/hooks/context"; interface LevelDBStat { Level: string; Files: string; "Size(MB)": string; "Time(sec)": string; "Read(MB)": string; "Write(MB)": string; } interface StorageMetrics { ext_cache_hit_ratio: number; level_db_approx_mem_size: number; level_db_stats: LevelDBStat[]; max_resident_set_size: number; resident_set_size: number; num_reads: number; num_writes: number; } interface MemoryMetric { name: string; value: string | number; description: string; link: string; } interface CacheHitRatioProps { ratio: number; } interface LevelDbCardProps { levelDbSize: number; rssSize: number; numReads: number; numWrites: number; } interface LevelDBStatsTableProps { stats: LevelDBStat[]; } const CacheHitGauge = () => { const [isCalculating, setIsCalculating] = useState(false); const [p99Value, setP99Value] = useState(0); const [animatedValue, setAnimatedValue] = useState(0); const calculateP99 = async () => { setIsCalculating(true); setP99Value(0); setAnimatedValue(0); try { const response = await middlewareApi.post("/transactions/calculateP99", { samples: 100, }); const data = response?.data; const finalValue = data.p99; setP99Value(finalValue); setIsCalculating(false); } catch (error) { console.error("Error calculating P99:", error); setIsCalculating(false); } }; function calculatPercent() { return p99Value / 5000; } return ( <Card className="flex-1"> <CardHeader> <CardTitle>P99 Latency Test (Set Method)</CardTitle> </CardHeader> <CardContent className="p-6 flex flex-col items-center justify-center"> {!isCalculating && p99Value === 0 ? ( <Button onClick={calculateP99} className="w-32 h-32 rounded-full text-2xl font-bold" disabled={isCalculating} > GO </Button> ) : ( <div className="w-full h-48 relative"> <GaugeChart style={{ height: "12rem" }} id="p99-gauge-chart" nrOfLevels={10} colors={["#10B981", "#FBBF24", "#EF4444"]} percent={calculatPercent()} textColor="#000000" needleColor="#5C6BC0" needleBaseColor="#3949AB" arcWidth={0.2} formatTextValue={() => ""} /> <div className="absolute inset-0 flex items-center justify-center"> <div className="text-xl font-bold"> {isCalculating ? `${animatedValue.toFixed(1)}s` : `${p99Value.toFixed(1)}ms`} </div> </div> </div> )} {isCalculating && ( <p className="mt-1 text-center text-muted-foreground"> Calculating P99 latency... </p> )} {!isCalculating && p99Value > 0 && ( <Button onClick={calculateP99} className="mt-1"> Recalculate </Button> )} </CardContent> </Card> ); }; export function StorageEngineMetrics() { const mode = useContext<ModeType>(ModeContext); const { toast } = useToast(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [refresh, setRefresh] = useState(false); const [data, setData] = useState<StorageMetrics>(null); useEffect(() => { if (mode === "offline") { toast({ title: "Offline Mode", description: "Storage Engine Metrics Cannot be fetched in offline mode", }); return; } fetchTransactionData(); }, [refresh]); async function fetchTransactionData() { try { setLoading(true); const response = await middlewareApi.post( "/statsExporter/getTransactionData" ); setData(response?.data); toast({ title: "Data Updated", description: "Storage Engine Metrics Fetched Successfully", }); setLoading(false); } catch (error) { setLoading(false); setError(error); } } return ( <Card className="flex-1 w-full max-w-8xl mx-auto bg-gradient-to-br from-slate-900 to-slate-950 text-white shadow-xl"> <CardHeader className="flex flex-row items-center justify-between pb-2"> <div className="flex items-center gap-2"> <Database className="w-6 h-6 text-blue-400" /> <CardTitle className="text-2xl font-bold"> {" "} Storage Engine Metrics </CardTitle> </div> <div className="flex items-center gap-2"> <Button variant="outline" size="icon" onClick={() => setRefresh((prev) => !prev)} > <RefreshCcw /> </Button> <TooltipProvider> <Tooltip> <TooltipTrigger asChild> <button className="p-2 bg-slate-700 text-slate-400 hover:text-white hover:bg-slate-600 transition-colors duration-200 ease-in-out rounded" //onClick={() => window.open("https://gmail.com", "_blank")} > <Info size={18.5} /> </button> </TooltipTrigger> <TooltipContent> <p>Click for more information about these metrics</p> </TooltipContent> </Tooltip> </TooltipProvider> </div> </CardHeader> {loading ? ( <Loader className="h-[400px]" /> ) : ( <CardContent className="space-y-4"> <div className="flex flex-row justify-between space-x-2"> <CacheHitRatio ratio={data?.ext_cache_hit_ratio} /> <LevelDBSizeCard size={data?.level_db_approx_mem_size} /> <LevelDbCard levelDbSize={data?.level_db_approx_mem_size} rssSize={data?.resident_set_size || 0} numReads={data?.num_reads} numWrites={data?.num_writes} /> <CacheHitGauge /> </div> <div className="flex flex-row justify-between space-x-2"> {/* <LevelDBStatsTable stats={data?.level_db_stats} /> */} </div> </CardContent> )} </Card> ); } export function CacheHitRatio({ ratio }: CacheHitRatioProps) { const percentage = Math.round(ratio * 100); return ( <Card className="w-full max-w-md flex-1"> <CardHeader> <CardTitle>Cache Hit Ratio</CardTitle> </CardHeader> <CardContent> <div className="flex items-center justify-between mb-2"> <span className="text-sm font-medium">Hit Rate</span> <span className="text-2xl font-bold">{percentage || 0}%</span> </div> <Progress value={percentage || 0} className="h-2" /> <p className="mt-2 text-sm text-muted-foreground"> {percentage || 0}% of requests are served from the LRU cache. </p> </CardContent> </Card> ); } export function LevelDBStatsTable({ stats }: LevelDBStatsTableProps) { if (!stats) { return ( <Card> <NotFound onRefresh={function (): void { throw new Error("Function not implemented."); }} /> </Card> ); } return ( <Card className="w-full max-w-md flex-1"> <CardHeader> <CardTitle>Level DB Stats</CardTitle> </CardHeader> <CardContent> <Table> <TableHeader> <TableRow> <TableHead>Level</TableHead> <TableHead>Files</TableHead> <TableHead>Size (MB)</TableHead> <TableHead>Time (sec)</TableHead> <TableHead>Read (MB)</TableHead> <TableHead>Write (MB)</TableHead> </TableRow> </TableHeader> <TableBody> {stats.map((stat) => ( <TableRow key={stat.Level}> <TableCell>{stat.Level}</TableCell> <TableCell>{stat.Files}</TableCell> <TableCell>{stat["Size(MB)"]}</TableCell> <TableCell>{stat["Time(sec)"]}</TableCell> <TableCell>{stat["Read(MB)"]}</TableCell> <TableCell>{stat["Write(MB)"]}</TableCell> </TableRow> ))} </TableBody> </Table> </CardContent> </Card> ); } //TODO: refactor export function LevelDbCard({ levelDbSize, rssSize, numReads, numWrites, }: LevelDbCardProps) { const size = Math.round(levelDbSize / 1000); const memoryMetrics: MemoryMetric[] = [ // { // name: "Level DB Approx Size", // value: String(size) + " KB", // description: "Estimated size of the LevelDB database", // link: "https://github.com/google/leveldb", // }, { name: "RSS", value: rssSize ? String(rssSize || 0) + " MB" : 0, description: "Current Resident Set Size (physical memory used)", link: "https://en.wikipedia.org/wiki/Resident_set_size", }, { name: "Number of Reads by process", value: numReads, description: "Number of times the file system had to read from the disk on behalf of processes.", link: "https://www.gnu.org/software/libc/manual/html_node/Resource-Usage.html", }, { name: "Number of Writes by process", value: numWrites, description: "The number of times the file system had to write to the disk on behalf of processes.", link: "https://www.gnu.org/software/libc/manual/html_node/Resource-Usage.html", }, ]; return ( <Card className="w-full max-w-2xl"> <CardHeader> <CardTitle>Memory Footprint</CardTitle> </CardHeader> <CardContent> <Table> <TableHeader> <TableRow> <TableHead>Metric</TableHead> <TableHead>Value</TableHead> <TableHead>Info</TableHead> </TableRow> </TableHeader> <TableBody> {memoryMetrics.map((metric) => ( <TableRow key={metric.name}> <TableCell>{metric.name}</TableCell> <TableCell>{metric.value || 0}</TableCell> <TableCell> <TooltipProvider> <Tooltip> <TooltipTrigger asChild> <Link to={metric.link} target="_blank" rel="noopener noreferrer" > <InfoIcon className="h-4 w-4 text-muted-foreground hover:text-foreground" /> </Link> </TooltipTrigger> <TooltipContent> <p>{metric.description}</p> </TooltipContent> </Tooltip> </TooltipProvider> </TableCell> </TableRow> ))} </TableBody> </Table> </CardContent> </Card> ); } interface LevelDBSizeCardProps { size: number; // Size of the database, e.g., "256 MB" } export function LevelDBSizeCard({ size }: LevelDBSizeCardProps) { return ( <Card className="w-full max-w-xs"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle className="text-sm font-medium">Database Size</CardTitle> <Database className="h-4 w-4 text-muted-foreground" /> </CardHeader> <div className="flex flex-col justify-center items-center flex-grow p-6"> <div className="text-3xl sm:text-4xl md:text-5xl font-bold text-center"> {Math.floor((size || 0) / 1000)} KB </div> <p className="text-xs text-muted-foreground mt-2">LevelDB Storage</p> <p className="text-sm text-center mt-4 max-w-xs text-muted-foreground"> Size of data present in LevelDB with compaction in place </p> </div> </Card> ); } interface LevelDBStatsProps { totalSize: string; stats: LevelDBStat[]; className?: string; } export function LevelDBStats({ totalSize, stats, className, }: LevelDBStatsProps) { const totalFiles = stats.reduce((sum, stat) => sum + stat.Files, 0); const totalSizeMB = stats.reduce((sum, stat) => sum + stat["Size(MB)"], 0); return ( <div className={`space-y-4 ${className}`}> <Card className="w-full"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle className="text-2xl font-bold"> LevelDB Statistics </CardTitle> <Database className="h-6 w-6 text-muted-foreground" /> </CardHeader> <CardContent> <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> <StatsCard title="Total Size" value={totalSize} description="Overall database size" /> <StatsCard title="Total Files" value={totalFiles.toString()} description="Number of SSTable files" /> <StatsCard title="Total Size (MB)" value={`${totalSizeMB.toFixed(2)} MB`} description="Sum of all level sizes" /> </div> </CardContent> </Card> <Card className="w-full"> <CardHeader> <CardTitle>Level DB Stats</CardTitle> </CardHeader> <CardContent> <Table> <TableHeader> <TableRow> <TableHead>Level</TableHead> <TableHead>Files</TableHead> <TableHead>Size (MB)</TableHead> <TableHead>Time (sec)</TableHead> <TableHead>Read (MB)</TableHead> <TableHead>Write (MB)</TableHead> </TableRow> </TableHeader> <TableBody> {stats.map((stat) => ( <TableRow> <TableCell>{stat.Level}</TableCell> <TableCell>{stat.Files}</TableCell> <TableCell>{stat["Size(MB)"]}</TableCell> <TableCell>{stat["Time(sec)"]}</TableCell> <TableCell>{stat["Read(MB)"]}</TableCell> <TableCell>{stat["Write(MB)"]}</TableCell> </TableRow> ))} </TableBody> </Table> </CardContent> </Card> </div> ); } function StatsCard({ title, value, description, }: { title: string; value: string; description: string; }) { return ( <Card> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle className="text-sm font-medium">{title}</CardTitle> </CardHeader> <CardContent> <div className="text-2xl font-bold">{value}</div> <p className="text-xs text-muted-foreground">{description}</p> </CardContent> </Card> ); }