count_project_size.mjs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import readline from 'node:readline'
  2. import { ObjectId, db } from '../app/src/infrastructure/mongodb.js'
  3. import ProjectEntityHandler from '../app/src/Features/Project/ProjectEntityHandler.js'
  4. import ProjectGetter from '../app/src/Features/Project/ProjectGetter.js'
  5. import Errors from '../app/src/Features/Errors/Errors.js'
  6. import FileStoreHandler from '../app/src/Features/FileStore/FileStoreHandler.js'
  7. // Handles a list of project IDs from stdin, one per line, and outputs the count of files and docs
  8. // in the project, along with the aggregated size in bytes for all files and docs.
  9. //
  10. // It outputs to stderr, so that the logging junk can be piped elsewhere - e.g., running like:
  11. // node scripts/count_project_size.mjs < /tmp/project_ids.txt /dev/null 2> /tmp/output.txt
  12. //
  13. // The output format is line-per-project with data separated by a single space, containing:
  14. // - projectId
  15. // - file count
  16. // - deleted files count
  17. // - doc count
  18. // - deleted docs count
  19. // - total size in bytes of (non deleted) files
  20. // - total size in bytes of (non deleted) docs
  21. async function countProjectFiles() {
  22. const rl = readline.createInterface({
  23. input: process.stdin,
  24. })
  25. for await (const projectId of rl) {
  26. try {
  27. const project = await ProjectGetter.promises.getProject(projectId)
  28. if (!project) {
  29. throw new Errors.NotFoundError('project not found')
  30. }
  31. const { files, docs } =
  32. ProjectEntityHandler.getAllEntitiesFromProject(project)
  33. const [fileSize, docSize] = await Promise.all([
  34. countFilesSize(files, projectId),
  35. countDocsSizes(docs),
  36. ])
  37. console.error(
  38. projectId,
  39. files.length,
  40. docs.length,
  41. (project.deletedDocs && project.deletedDocs.length) || 0,
  42. fileSize,
  43. docSize
  44. )
  45. } catch (err) {
  46. if (err instanceof Errors.NotFoundError) {
  47. console.error(projectId, 'NOTFOUND')
  48. } else {
  49. console.log(projectId, 'ERROR', err.name, err.message)
  50. }
  51. }
  52. }
  53. }
  54. async function countFilesSize(files, projectId) {
  55. if (!files?.length > 0) {
  56. return 0
  57. }
  58. const ids = files.map(fileObject => fileObject.file._id)
  59. let totalFileSize = 0
  60. for (const fileId of ids) {
  61. const contentLength = await FileStoreHandler.promises.getFileSize(
  62. projectId,
  63. fileId
  64. )
  65. const size = parseInt(contentLength, 10)
  66. if (isNaN(size)) {
  67. throw new Error(
  68. `Unable to fetch file size for fileId=${fileId} and projectId=${projectId}`
  69. )
  70. }
  71. totalFileSize += size
  72. }
  73. return totalFileSize
  74. }
  75. async function countDocsSizes(docs) {
  76. if (!docs?.length > 0) {
  77. return 0
  78. }
  79. const ids = docs.map(docObject => docObject.doc._id)
  80. let totalDocSize = 0
  81. for (const docId of ids) {
  82. const result = await db.docs.aggregate([
  83. {
  84. $match: { _id: new ObjectId(docId) },
  85. },
  86. {
  87. $project: {
  88. lineSizeInBytes: {
  89. $reduce: {
  90. input: { $ifNull: ['$lines', []] },
  91. initialValue: 0,
  92. in: {
  93. $add: ['$$value', { $strLenBytes: '$$this' }],
  94. },
  95. },
  96. },
  97. },
  98. },
  99. ])
  100. const { lineSizeInBytes } = await result.next()
  101. if (isNaN(lineSizeInBytes)) {
  102. throw new Error(`Unable to fetch 'lineSizeInBytes' for docId=${docId}`)
  103. }
  104. totalDocSize += lineSizeInBytes
  105. }
  106. return totalDocSize
  107. }
  108. try {
  109. await countProjectFiles()
  110. process.exit(0)
  111. } catch (error) {
  112. console.log('Aiee, something went wrong!', error)
  113. process.exit(1)
  114. }