|
2 | 2 | * Contains helper functions to work with the `pci.ids` file
|
3 | 3 | * ({@link https://pci-ids.ucw.cz})
|
4 | 4 | */
|
5 |
| -import axios, { AxiosError } from 'axios' |
6 |
| -import path from 'path' |
7 |
| - |
8 |
| -import { downloadFile, DAYS } from '../../inet/downloader' |
9 |
| -import { toolsPath } from 'backend/constants' |
| 5 | +import { resolveTxt } from 'dns' |
| 6 | +import { promisify } from 'util' |
| 7 | +import CacheStore from 'backend/cache' |
10 | 8 |
|
11 | 9 | import type { PartialGpuInfo } from './index'
|
12 | 10 | import type { GPUInfo } from '../index'
|
13 | 11 |
|
14 |
| -const pciIdsMap: Record< |
15 |
| - string, |
16 |
| - { |
17 |
| - vendorName: string |
18 |
| - devices: Record< |
19 |
| - string, |
20 |
| - { |
21 |
| - deviceName: string |
22 |
| - subsystems: Record<`${string} ${string}`, string> |
23 |
| - } |
24 |
| - > |
25 |
| - } |
26 |
| -> = {} |
| 12 | +const resolveTxtAsync = promisify(resolveTxt) |
27 | 13 |
|
28 |
| -async function getPicIds(): Promise<typeof pciIdsMap | null> { |
29 |
| - if (Object.keys(pciIdsMap).length !== 0) return pciIdsMap |
| 14 | +type DeviceNameCacheKey = |
| 15 | + | `${string}_${string}_${string}_${string}` // DeviceSubID_VendorSubID_DeviceID_VendorID |
| 16 | + | `${string}_${string}` // DeviceID_VendorID |
| 17 | +export const deviceNameCache = new CacheStore<string, DeviceNameCacheKey>( |
| 18 | + 'pci_ids_device', |
| 19 | + 60 * 24 * 7 // 7 days |
| 20 | +) |
| 21 | +export const vendorNameCache = new CacheStore<string, string>( |
| 22 | + 'pci_ids_vendor', |
| 23 | + 60 * 24 * 7 |
| 24 | +) |
30 | 25 |
|
31 |
| - const pciIdsFile = await downloadFile('https://pci-ids.ucw.cz/v2.2/pci.ids', { |
32 |
| - axiosConfig: { |
33 |
| - responseType: 'text' |
34 |
| - }, |
35 |
| - writeToFile: path.join(toolsPath, 'pci.ids'), |
36 |
| - maxCache: 30 * DAYS |
37 |
| - }).catch((error) => error as AxiosError) |
38 |
| - if (axios.isAxiosError(pciIdsFile)) return null |
| 26 | +async function pciIdDnsQuery(pre: string): Promise<string | false> { |
| 27 | + const records: string[][] = await resolveTxtAsync( |
| 28 | + `${pre}.pci.id.ucw.cz` |
| 29 | + ).catch(() => []) |
| 30 | + return records[0]?.[0]?.replace(/^i=/, '') ?? false |
| 31 | +} |
39 | 32 |
|
40 |
| - let currentVendor: string | null = null |
41 |
| - let currentDevice: string | null = null |
42 |
| - for (const line of pciIdsFile.split('\n')) { |
43 |
| - // Skip comments and empty lines |
44 |
| - if (line.startsWith('#')) continue |
45 |
| - if (line === '') continue |
| 33 | +async function lookupDeviceString( |
| 34 | + vendorId: string, |
| 35 | + deviceId: string, |
| 36 | + subvendorId?: string, |
| 37 | + subdeviceId?: string |
| 38 | +): Promise<string | false> { |
| 39 | + async function lookupKey(key: DeviceNameCacheKey): Promise<string | false> { |
| 40 | + const cached = deviceNameCache.get(key) |
| 41 | + if (cached) return cached |
46 | 42 |
|
47 |
| - // Case 1: Line describes a new vendor |
48 |
| - const vendorMatch = line.match(/^(.{4}) {2}(.*)$/) |
49 |
| - const vendorId = vendorMatch?.[1] |
50 |
| - const vendorName = vendorMatch?.[2] |
51 |
| - if (vendorId && vendorName) { |
52 |
| - pciIdsMap[vendorId] = { vendorName, devices: {} } |
53 |
| - currentVendor = vendorId |
54 |
| - continue |
55 |
| - } |
| 43 | + const result = await pciIdDnsQuery(key.replaceAll('_', '.')) |
| 44 | + if (result) deviceNameCache.set(key, result) |
| 45 | + return result |
| 46 | + } |
56 | 47 |
|
57 |
| - // Case 2: Line describes a new device |
58 |
| - const deviceMatch = line.match(/^\t(.{4}) {2}(.*)$/) |
59 |
| - const deviceId = deviceMatch?.[1] |
60 |
| - const deviceName = deviceMatch?.[2] |
61 |
| - if (deviceId && deviceName && currentVendor) { |
62 |
| - const vendorObj = pciIdsMap[currentVendor] |
63 |
| - if (!vendorObj) continue |
64 |
| - vendorObj.devices[deviceId] = { deviceName, subsystems: {} } |
65 |
| - currentDevice = deviceId |
66 |
| - continue |
67 |
| - } |
| 48 | + if (!subvendorId || !subdeviceId) return lookupKey(`${deviceId}_${vendorId}`) |
68 | 49 |
|
69 |
| - // Case 3: Line describes a new subsystem |
70 |
| - const subsystemMatch = line.match(/\t\t(.{4}) (.{4}) {2}(.*)$/) |
71 |
| - if (!subsystemMatch) continue |
72 |
| - const [, subvendor, subdevice, subsystemName] = subsystemMatch |
73 |
| - if ( |
74 |
| - subvendor && |
75 |
| - subdevice && |
76 |
| - subsystemName && |
77 |
| - currentVendor && |
78 |
| - currentDevice |
79 |
| - ) { |
80 |
| - const deviceObj = pciIdsMap[currentVendor]?.devices[currentDevice] |
81 |
| - if (!deviceObj) continue |
82 |
| - deviceObj.subsystems[`${subvendor} ${subdevice}`] = subsystemName |
83 |
| - } |
84 |
| - } |
| 50 | + // If we have a subdevice and subvendor ID, try getting a name with them first |
| 51 | + const resultWithSubIDs = await lookupKey( |
| 52 | + `${subdeviceId}_${subvendorId}_${deviceId}_${vendorId}` |
| 53 | + ) |
| 54 | + if (resultWithSubIDs) return resultWithSubIDs |
85 | 55 |
|
86 |
| - return pciIdsMap |
| 56 | + // If there's no name for this specific subdevice and subvendor ID, try |
| 57 | + // with just the device and vendor ID |
| 58 | + const resultWithoutSubIDs = await lookupKey(`${deviceId}_${vendorId}`) |
| 59 | + // Update the cache entry with subIDs, to avoid re-requesting them all the |
| 60 | + // time |
| 61 | + if (resultWithoutSubIDs) |
| 62 | + deviceNameCache.set( |
| 63 | + `${subdeviceId}_${subvendorId}_${deviceId}_${vendorId}`, |
| 64 | + resultWithoutSubIDs |
| 65 | + ) |
| 66 | + return resultWithoutSubIDs |
| 67 | +} |
| 68 | + |
| 69 | +async function lookupVendorString(vendorId: string): Promise<string | false> { |
| 70 | + const cached = vendorNameCache.get(vendorId) |
| 71 | + if (cached) return cached |
| 72 | + |
| 73 | + const result = await pciIdDnsQuery(vendorId.replaceAll('_', '.')) |
| 74 | + if (result) vendorNameCache.set(vendorId, result) |
| 75 | + return result |
87 | 76 | }
|
88 | 77 |
|
89 | 78 | async function populateDeviceAndVendorName(
|
90 | 79 | partialGpus: PartialGpuInfo[]
|
91 | 80 | ): Promise<GPUInfo[]> {
|
92 |
| - const pciIds = await getPicIds() |
93 |
| - if (pciIds === null) return partialGpus |
94 |
| - |
95 | 81 | const fullGpuInfo: GPUInfo[] = []
|
96 | 82 | for (const gpu of partialGpus) {
|
97 | 83 | const vendorId = gpu.vendorId.toLowerCase()
|
98 | 84 | const deviceId = gpu.deviceId.toLowerCase()
|
99 | 85 | const subvendorId = gpu.subvendorId?.toLowerCase()
|
100 | 86 | const subdeviceId = gpu.subdeviceId?.toLowerCase()
|
101 |
| - const vendor = pciIds[vendorId] |
102 |
| - const device = pciIds[vendorId]?.devices[deviceId] |
103 |
| - const subsystem = |
104 |
| - pciIds[vendorId]?.devices[deviceId]?.subsystems[ |
105 |
| - `${subvendorId} ${subdeviceId}` |
106 |
| - ] |
| 87 | + |
| 88 | + const deviceString = await lookupDeviceString( |
| 89 | + vendorId, |
| 90 | + deviceId, |
| 91 | + subvendorId, |
| 92 | + subdeviceId |
| 93 | + ) |
| 94 | + const vendorString = await lookupVendorString(vendorId) |
| 95 | + if (!deviceString || !vendorString) continue |
| 96 | + |
107 | 97 | fullGpuInfo.push({
|
108 | 98 | ...gpu,
|
109 |
| - deviceString: subsystem ?? device?.deviceName, |
110 |
| - vendorString: vendor?.vendorName |
| 99 | + deviceString, |
| 100 | + vendorString |
111 | 101 | })
|
112 | 102 | }
|
113 | 103 | return fullGpuInfo
|
|
0 commit comments