count_project_size.mjs 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  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 HistoryManager from '../app/src/Features/History/HistoryManager.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. let totalFileSize = 0
  59. for (const { file } of files) {
  60. const { contentLength } =
  61. await HistoryManager.promises.requestBlobWithProjectId(
  62. projectId,
  63. file.hash,
  64. 'HEAD'
  65. )
  66. totalFileSize += contentLength
  67. }
  68. return totalFileSize
  69. }
  70. async function countDocsSizes(docs) {
  71. if (!docs?.length > 0) {
  72. return 0
  73. }
  74. const ids = docs.map(docObject => docObject.doc._id)
  75. let totalDocSize = 0
  76. for (const docId of ids) {
  77. const result = await db.docs.aggregate([
  78. {
  79. $match: { _id: new ObjectId(docId) },
  80. },
  81. {
  82. $project: {
  83. lineSizeInBytes: {
  84. $reduce: {
  85. input: { $ifNull: ['$lines', []] },
  86. initialValue: 0,
  87. in: {
  88. $add: ['$$value', { $strLenBytes: '$$this' }],
  89. },
  90. },
  91. },
  92. },
  93. },
  94. ])
  95. const { lineSizeInBytes } = await result.next()
  96. if (isNaN(lineSizeInBytes)) {
  97. throw new Error(`Unable to fetch 'lineSizeInBytes' for docId=${docId}`)
  98. }
  99. totalDocSize += lineSizeInBytes
  100. }
  101. return totalDocSize
  102. }
  103. try {
  104. await countProjectFiles()
  105. process.exit(0)
  106. } catch (error) {
  107. console.log('Aiee, something went wrong!', error)
  108. process.exit(1)
  109. }